001package jmri.jmrix.mrc; 002 003import org.slf4j.Logger; 004import org.slf4j.LoggerFactory; 005 006/** 007 * Encodes and decoders messages to an MRC command station. 008 * <p> 009 * Some of the message formats used in this class are Copyright MRC, Inc. and 010 * used with permission as part of the JMRI project. That permission does not 011 * extend to uses in other software products. If you wish to use this code, 012 * algorithm or these message formats outside of JMRI, please contact MRC Inc 013 * for separate permission. 014 * 015 * @author Bob Jacobsen Copyright (C) 2001, 2004 016 * @author Kevin Dickerson Copyright (C) 2014 017 * @author kcameron Copyright (C) 2014 018 */ 019public class MrcMessage { 020 021 // create a new one 022 public MrcMessage(int len) { 023 if (len < 1) { 024 log.error("invalid length in call to ctor: {}", len); // NOI18N 025 } 026 _nDataChars = len; 027 _dataChars = new int[len]; 028 } 029 030 // copy one 031 public MrcMessage(MrcMessage original) { 032 this(original._dataChars); 033 } 034 035 public MrcMessage(int[] contents) { 036 this(contents.length); 037 for (int i = 0; i < contents.length; i++) { 038 this.setElement(i, contents[i]); 039 } 040 } 041 042 public MrcMessage(byte[] contents) { 043 this(contents.length); 044 for (int i = 0; i < contents.length; i++) { 045 this.setElement(i, contents[i] & 0xFF); 046 } 047 } 048 049 MrcTrafficListener source = null; 050 051 public void setSource(MrcTrafficListener s) { 052 source = s; 053 } 054 055 public MrcTrafficListener getSource() { 056 return source; 057 } 058 059 int msgClass = ~0; 060 061 void setMessageClass(int i) { 062 msgClass = i; 063 } 064 065 public int getMessageClass() { 066 return msgClass; 067 } 068 069 public void replyNotExpected() { 070 replyExpected = false; 071 } 072 073 boolean replyExpected = true; 074 075 public boolean isReplyExpected() { 076 return replyExpected; 077 } 078 079 int SHORT_TIMEOUT = 150; 080 int SHORT_PROG_TIMEOUT = 4000; 081 082 int timeout = SHORT_TIMEOUT; 083 084 void setTimeout(int i) { 085 timeout = i; 086 } 087 088 public int getTimeout() { 089 return timeout; 090 } 091 092 int retries = 3; 093 094 public int getRetries() { 095 return retries; 096 } 097 098 public void setRetries(int i) { 099 retries = i; 100 } 101 102 boolean inError = false; 103 104 public void setMessageInError() { 105 inError = true; 106 } 107 108 public boolean isPacketInError() { 109 return inError; 110 } 111 112 int putHeader(int[] insert) { 113 int i = 0; 114 for (i = 0; i < insert.length; i++) { 115 this.setElement(i, insert[i]); 116 } 117 return i; 118 } 119 120 @Override 121 public String toString() { 122 return MrcPackets.toString(this); 123 } 124 125 static public MrcMessage getSendSpeed128(int addressLo, int addressHi, int speed) { 126 MrcMessage m = new MrcMessage(MrcPackets.getThrottlePacketLength()); 127 m.setMessageClass(MrcInterface.THROTTLEINFO); 128 int i = m.putHeader(MrcPackets.THROTTLEPACKETHEADER); 129 130 m.setElement(i++, addressHi); 131 m.setElement(i++, 0x00); 132 m.setElement(i++, addressLo); 133 m.setElement(i++, 0x00); 134 m.setElement(i++, speed); 135 m.setElement(i++, 0x00); 136 m.setElement(i++, 0x02); 137 m.setElement(i++, 0x00); 138 m.setElement(i++, getCheckSum(addressHi, addressLo, speed, 0x02)); 139 m.setElement(i++, 0x00); 140 // m.setTimeout(100); 141 return m; 142 } 143 144 static public MrcMessage getSendSpeed28(int addressLo, int addressHi, int speed, boolean fwd) { 145 MrcMessage m = new MrcMessage(MrcPackets.getThrottlePacketLength()); 146 m.setMessageClass(MrcInterface.THROTTLEINFO); 147 int i = m.putHeader(MrcPackets.THROTTLEPACKETHEADER); 148 149 int speedC = (speed & 0x1E) >> 1; 150 int c = (speed & 0x01) << 4; 151 speedC = speedC + c; 152 speedC = (fwd ? 0x60 : 0x40) | speedC; 153 154 m.setElement(i++, addressHi); 155 m.setElement(i++, 0x00); 156 m.setElement(i++, addressLo); 157 m.setElement(i++, 0x00); 158 m.setElement(i++, speedC); 159 m.setElement(i++, 0x00); 160 m.setElement(i++, 0x00); 161 m.setElement(i++, 0x00); 162 m.setElement(i++, getCheckSum(addressHi, addressLo, speedC, 0x00)); 163 m.setElement(i++, 0x00); 164 // m.setTimeout(100); 165 return m; 166 } 167 168 static public MrcMessage getSendFunction(int group, int addressLo, int addressHi, int data) { 169 MrcMessage m = new MrcMessage(MrcPackets.getFunctionPacketLength()); 170 m.setMessageClass(MrcInterface.THROTTLEINFO); 171 m.replyNotExpected(); 172 int i = 0; 173 switch (group) { 174 case 1: 175 i = m.putHeader(MrcPackets.FUNCTIONGROUP1PACKETHEADER); 176 break; 177 case 2: 178 i = m.putHeader(MrcPackets.FUNCTIONGROUP2PACKETHEADER); 179 break; 180 case 3: 181 i = m.putHeader(MrcPackets.FUNCTIONGROUP3PACKETHEADER); 182 break; 183 case 4: 184 i = m.putHeader(MrcPackets.FUNCTIONGROUP4PACKETHEADER); 185 break; 186 case 5: 187 i = m.putHeader(MrcPackets.FUNCTIONGROUP5PACKETHEADER); 188 break; 189 case 6: 190 i = m.putHeader(MrcPackets.FUNCTIONGROUP6PACKETHEADER); 191 break; 192 default: 193 log.error("Invalid function group: {}", group); // NOI18N 194 return null; 195 } 196 197 m.setElement(i++, addressHi); 198 m.setElement(i++, 0x00); 199 m.setElement(i++, addressLo); 200 m.setElement(i++, 0x00); 201 m.setElement(i++, data); 202 m.setElement(i++, 0x00); 203 m.setElement(i++, getCheckSum(addressHi, addressLo, data, 0x00)); 204 m.setElement(i++, 0x00); 205 // m.setTimeout(100); 206 return m; 207 } 208 209 static int getCheckSum(int addressHi, int addressLo, int data1, int data2) { 210 int address = addressHi ^ addressLo; 211 int data = data1 ^ data2; 212 return (address ^ data); 213 } 214 215 static public MrcMessage getReadCV(int cv) { //R xxx 216 int cvLo = (cv); 217 int cvHi = (cv >> 8); 218 219 MrcMessage m = new MrcMessage(MrcPackets.getReadCVPacketLength()); 220 m.setMessageClass(MrcInterface.PROGRAMMING); 221 m.setTimeout(LONG_TIMEOUT); 222 //m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE); 223 int i = m.putHeader(MrcPackets.READCVHEADER); 224 225 m.setElement(i++, cvHi); 226 m.setElement(i++, 0x00); 227 m.setElement(i++, cvLo); 228 m.setElement(i++, 0x00); 229 m.setElement(i++, getCheckSum(0x00, 0x00, cvHi, cvLo)); 230 m.setElement(i++, 0x00); 231 return m; 232 } 233 234 static public MrcMessage getPOM(int addressLo, int addressHi, int cv, int val) { 235 MrcMessage m = new MrcMessage(MrcPackets.getWriteCVPOMPacketLength()); 236 m.setMessageClass(MrcInterface.PROGRAMMING); 237 int i = m.putHeader(MrcPackets.WRITECVPOMHEADER); 238 239 cv--; 240 m.setElement(i++, addressHi); 241 m.setElement(i++, 0x00); 242 m.setElement(i++, addressLo); 243 m.setElement(i++, 0x00); 244 m.setElement(i++, 0xEC); 245 m.setElement(i++, 0x00); 246 m.setElement(i++, cv); 247 m.setElement(i++, 0x00); 248 m.setElement(i++, val); 249 m.setElement(i++, 0x00); 250 int checksum = getCheckSum(addressHi, addressLo, 0xEC, cv); 251 checksum = getCheckSum(checksum, val, 0x00, 0x00); 252 m.setElement(i++, checksum); 253 return m; 254 } 255 256 static public MrcMessage getWriteCV(int cv, int val) { 257 MrcMessage m = new MrcMessage(MrcPackets.getWriteCVPROGPacketLength()); 258 m.setMessageClass(MrcInterface.PROGRAMMING); 259 int i = m.putHeader(MrcPackets.WRITECVPROGHEADER); 260 261 int cvLo = cv; 262 int cvHi = cv >> 8; 263 264 m.setElement(i++, cvHi); 265 m.setElement(i++, 0x00); 266 m.setElement(i++, cvLo); 267 m.setElement(i++, 0x00); 268 m.setElement(i++, val); 269 m.setElement(i++, 0x00); 270 m.setElement(i++, getCheckSum(cvHi, cvLo, val, 0x00)); 271 return m; 272 } 273 274 static protected final int LONG_TIMEOUT = 65000; // e.g. for programming options 275 276 public boolean validCheckSum() { 277 if (getNumDataElements() > 6) { 278 int result = 0; 279 for (int i = 4; i < getNumDataElements() - 2; i++) { 280 result = (getElement(i) & 255) ^ result; 281 } 282 if (result == (getElement(getNumDataElements() - 2) & 255)) { 283 return true; 284 } 285 } 286 return false; 287 } 288 289 public int value() { 290 int val = -1; 291 if (MrcPackets.startsWith(this, MrcPackets.READCVHEADERREPLY)) { 292 if (getElement(4) == getElement(6)) { 293 val = getElement(4) & 0xff; 294 } else { 295 log.error("Error in format of the returned CV value"); // NOI18N 296 } 297 } else { 298 log.error("Not a CV Read formated packet"); // NOI18N 299 } 300 return val; 301 } 302 303 public int getLocoAddress() { 304 if (getMessageClass() != MrcInterface.THROTTLEINFO && getMessageClass() != MrcInterface.PROGRAMMING) { 305 return -1; 306 } 307 int hi = getElement(4); 308 int lo = getElement(6); 309 if (hi == 0) { 310 return lo; 311 } else { 312 hi = (((hi & 255) - 192) << 8); 313 hi = hi + (lo & 255); 314 return hi; 315 } 316 } 317 318 public int getAccAddress() { 319 if (getMessageClass() != MrcInterface.TURNOUTS) { 320 return -1; 321 } 322 int lowbyte = (getElement(4) & 0xFF) & 0x3f; 323 int highbyte = ((getElement(6) & 0xFF) & 0x70) >> 4; 324 highbyte = ((~highbyte & 0x07) << 6); 325 326 int address = (((lowbyte + highbyte) - 1) << 2) + 1; 327 328 address += ((getElement(6) & 0xFF) & 0x07) >> 1; 329 return address; 330 } 331 332 public int getAccState() { 333 if (((getElement(6) & 0x07) & 0x01) == 0x01) { 334 return jmri.Turnout.CLOSED; 335 } else { 336 return jmri.Turnout.THROWN; 337 } 338 } 339 340 /** 341 * set the fast clock ratio ratio is integer and max of 60 and min of 1 342 * @param ratio value to set new clock speed 343 * 344 * @return new message to set the clock speed ratio 345 */ 346 static public MrcMessage setClockRatio(int ratio) { 347 if (ratio < 0 || ratio > 60) { 348 log.error("ratio number too large: {}", ratio); // NOI18N 349 } 350 MrcMessage m = new MrcMessage(MrcPackets.getSetClockRatioPacketLength()); 351 m.setMessageClass(MrcInterface.CLOCK); 352 int i = m.putHeader(MrcPackets.SETCLOCKRATIOHEADER); 353 354 m.setElement(i++, ratio); 355 m.setElement(i++, 0x00); 356 m.setElement(i++, getCheckSum(ratio, 0x00, 0x00, 0x00)); 357 m.replyNotExpected(); 358 return m; 359 } 360 361 /** 362 * set the fast time clock 363 * @param hour hour value for fast clock 364 * @param minute minute value for fast clock 365 * 366 * @return new message to set the hour/minutes of the fast clock 367 */ 368 static public MrcMessage setClockTime(int hour, int minute) { 369 if (hour < 0 || hour > 23) { 370 log.error("hour number out of range : {}", hour); // NOI18N 371 } 372 if (minute < 0 || minute > 59) { 373 log.error("minute number out of range : {}", minute); // NOI18N 374 } 375 MrcMessage m = new MrcMessage(MrcPackets.getSetClockTimePacketLength()); 376 m.setMessageClass(MrcInterface.CLOCK); 377 int i = m.putHeader(MrcPackets.SETCLOCKTIMEHEADER); 378 379 m.setElement(i++, hour); 380 m.setElement(i++, 0x00); 381 m.setElement(i++, minute); 382 m.setElement(i++, 0x00); 383 m.setElement(i++, getCheckSum(hour, 0x00, minute, 0x00)); 384 m.replyNotExpected(); 385 return m; 386 } 387 388 /** 389 * Toggle the AM/PM vs 24 hour mode 390 * 391 * @return MrcMessage 392 */ 393 static public MrcMessage setClockAmPm() { 394 MrcMessage m = new MrcMessage(MrcPackets.getSetClockAmPmPacketLength()); 395 m.setMessageClass(MrcInterface.CLOCK); 396 int i = m.putHeader(MrcPackets.SETCLOCKAMPMHEADER); 397 398 m.setElement(i++, 0x32); 399 m.setElement(i++, 0x00); 400 m.setElement(i++, getCheckSum(0x32, 0x00, 0x00, 0x00)); 401 m.replyNotExpected(); 402 return m; 403 } 404 405 /** 406 * Set Track Power Off/Emergency Stop 407 * 408 * @return MrcMessage 409 */ 410 static public MrcMessage setPowerOff() { 411 MrcMessage m = new MrcMessage(MrcPackets.getPowerOffPacketLength()); 412 m.setMessageClass(MrcInterface.POWER); 413 m.putHeader(MrcPackets.POWEROFF); 414 m.replyNotExpected(); 415 return m; 416 } 417 418 static public MrcMessage setPowerOn() { 419 MrcMessage m = new MrcMessage(MrcPackets.getPowerOffPacketLength()); 420 m.setMessageClass(MrcInterface.POWER); 421 m.putHeader(MrcPackets.POWERON); 422 m.replyNotExpected(); 423 return m; 424 } 425 426 /** 427 * Get a message for a "Switch Position Normal" command to a specific 428 * accessory decoder on the layout. 429 * @param address address of turnout 430 * @param closed position for the turnout 431 * @return new message for getting switch posistion 432 */ 433 static MrcMessage getSwitchMsg(int address, boolean closed) { 434 MrcMessage m = new MrcMessage(MrcPackets.getAccessoryPacketLength()); 435 m.setMessageClass(MrcInterface.TURNOUTS); 436 m.putHeader(MrcPackets.ACCESSORYPACKETHEADER); 437 byte[] packet = jmri.NmraPacket.accDecoderPkt(address, closed); 438 if (packet == null) { 439 return null; 440 } 441 m.setElement(4, packet[0]); 442 m.setElement(5, 0x00); 443 m.setElement(6, packet[1]); 444 m.setElement(7, 0x00); 445 m.setElement(8, packet[2]); 446 m.setElement(9, 0x00); 447 m.setRetries(2); 448 m.replyNotExpected(); 449 m.setByteStream(); 450 return m; 451 } 452 453 static MrcMessage getRouteMsg(int address, boolean closed) { 454 MrcMessage m = new MrcMessage(MrcPackets.getRouteControlPacketLength()); 455 m.setMessageClass(MrcInterface.TURNOUTS); 456 m.putHeader(MrcPackets.ROUTECONTROLPACKETHEADER); 457 458 int i = m.putHeader(MrcPackets.ROUTECONTROLPACKETHEADER); 459 m.setElement(i++, address); 460 m.setElement(i++, 0x00); 461 int state = closed ? 0x80 : 0x00; 462 m.setElement(i++, state); 463 m.setElement(i++, 0x00); 464 m.setElement(i++, getCheckSum(address, 0x00, state, 0x00)); 465 m.setElement(i++, 0x00); 466 m.setRetries(2); 467 m.replyNotExpected(); 468 m.setByteStream(); 469 return m; 470 } 471 472 static public MrcMessage setNoData() { 473 MrcMessage m = new MrcMessage(4); 474 m.setMessageClass(MrcInterface.POLL); 475 m.setElement(0, 0x00); 476 m.setElement(1, 0x00); 477 m.setElement(2, 0x00); 478 m.setElement(3, 0x00); 479//Message is throw away, so if it doesn't get transmited correctly then forget about it, don't attempt retry. 480 m.setTimeout(0); 481 m.setRetries(0); 482 m.setByteStream(); 483 return m; 484 } 485 486 byte[] byteStream; 487 488 void setByteStream() { 489 int len = getNumDataElements(); 490 byteStream = new byte[len]; 491 for (int i = 0; i < len; i++) { 492 byteStream[i] = (byte) getElement(i); 493 } 494 } 495 496 byte[] getByteStream() { 497 return byteStream; 498 } 499 500 public int getElement(int n) { 501 return _dataChars[n]; 502 } 503 504 // accessors to the bulk data 505 public int getNumDataElements() { 506 return _nDataChars; 507 } 508 509 public void setElement(int n, int v) { 510 _dataChars[n] = v; 511 } 512 513 // contents (private) 514 private int _nDataChars = 0; 515 private int _dataChars[] = null; 516 517 private final static Logger log = LoggerFactory.getLogger(MrcMessage.class); 518 519} 520 521 522