001package jmri.jmrix.zimo; 002 003/** 004 * Represents a single command or response to the Zimo Binary Protocol. 005 * <p> 006 * Content is represented with ints to avoid the problems with sign-extension 007 * that bytes have, and because a Java char is actually a variable number of 008 * bytes in Unicode. 009 * 010 * @author Kevin Dickerson Copyright (C) 2014 011 * 012 * Adapted by Sip Bosch for use with zimo MX-1 013 * 014 */ 015public class Mx1Message extends jmri.jmrix.NetMessage { 016 017 public Mx1Message(int len) { 018 this(len, Mx1Packetizer.ASCII); 019 } 020 021 /** 022 * Create a new object, representing a specific-length message. 023 * 024 * @param len Total bytes in message, including opcode and 025 * error-detection byte. 026 * @param protocol one of {@link Mx1Packetizer#ASCII} or 027 * {@link Mx1Packetizer#BINARY} 028 */ 029 public Mx1Message(int len, boolean protocol) { 030 super(len); 031 this.protocol = protocol; 032 if (!protocol) { 033 if (len > 15 || len < 0) { 034 log.error("Invalid length in ctor: {}", len); 035 } 036 } 037 } 038 039 boolean protocol = Mx1Packetizer.ASCII; 040 041 //Version 5 and above allow for a message size greater than 15 bytes 042 public Mx1Message(Integer[] contents) { 043 super(contents.length); 044 protocol = Mx1Packetizer.BINARY; 045 for (int i = 0; i < contents.length; i++) { 046 this.setElement(i, contents[i]); 047 } 048 } 049 050 //Version 5 and above allow for a message size greater than 15 bytes 051 public Mx1Message(byte[] contents) { 052 super(contents.length); 053 protocol = Mx1Packetizer.BINARY; 054 for (int i = 0; i < contents.length; i++) { 055 this.setElement(i, (contents[i] & 0xff)); 056 } 057 } 058 059 public boolean getLongMessage() { 060 if (protocol == Mx1Packetizer.BINARY) { 061 if ((getElement(1) & 0x80) == 0x80) { 062 return true; 063 } 064 } 065 return false; 066 } 067 068 final static int PRIMARY = 0x00; 069 final static int ACKREP1 = 0x40; 070 final static int REPLY2 = 0x20; 071 final static int ACK2 = 0x60; 072 073 /** 074 * Indicates where the message is to/from in the header byte. 075 * <p> 076 * Up to JMRI 4.3.5, this was doing {@code ((mod & MX1) == MX1)} for the 077 * first test, which is really 0 == 0 and always true. At that point it was 078 * changed to just check the bottom two bits. 079 * 080 * @return one of {@link #MX1}, {@link #MX8}, {@link #MX9} or 0x0F 081 */ 082 public int getModule() { 083 int mod = getElement(1) & 0x0F; 084 if ((mod & 0x03) == MX1) { 085 return MX1; 086 } 087 if ((mod & 0x03) == MX8) { 088 return MX8; 089 } 090 if ((mod & 0x03) == MX9) { 091 return MX9; 092 } 093 return mod; 094 } 095 096 public int getMessageType() { 097 return getElement(1) & 0x60; 098 } 099 100 public int getPrimaryMessage() { 101 return getElement(2); 102 } 103 104 /** 105 * Message to/from Command Station MX1 106 */ 107 static final int MX1 = 0x00; 108 109 /** 110 * Message to/from Accessory module MX8 111 */ 112 static final int MX8 = 0x01; 113 114 /** 115 * Message to/from Track Section module MX9 116 */ 117 static final int MX9 = 0x02; 118 119 /** 120 * Indicates the message source is a command station. {@value #CS} 121 * 122 * @see #messageSource() 123 */ 124 final static boolean CS = true; 125 /** 126 * Indicates the message source is a command station. {@value #PC} 127 * 128 * @see #messageSource() 129 */ 130 final static boolean PC = false; 131 132 /** 133 * Indicates the source of the message. 134 * 135 * @return {@link #PC} or {@link #CS} 136 */ 137 public boolean messageSource() { 138 if ((getElement(0) & 0x08) == 0x08) { 139 return PC; 140 } 141 return CS; 142 } 143 144 long timeStamp = 0L; 145 146 protected long getTimeStamp() { 147 return timeStamp; 148 } 149 150 protected void setTimeStamp(long ts) { 151 timeStamp = ts; 152 } 153 154 int retries = 3; 155 156 public int getRetry() { 157 return retries; 158 } 159 160 public void setRetries(int i) { 161 retries = i; 162 } 163 164 //byte sequenceNo = 0x00; 165 public boolean replyL1Expected() { 166 return true; 167 } 168 169 byte[] rawPacket; 170 171 public void setRawPacket(byte[] b) { 172 rawPacket = b; 173 } 174 175 protected byte[] getRawPacket() { 176 return rawPacket; 177 } 178 179 public void setSequenceNo(byte s) { 180 setElement(0, (s & 0xff)); 181 } 182 183 public int getSequenceNo() { 184 return (getElement(0) & 0xff); 185 } 186 187 boolean crcError = false; 188 189 public void setCRCError() { 190 crcError = true; 191 } 192 193 public boolean isCRCError() { 194 return crcError; 195 } 196 197 /** 198 * Check if the message has a valid parity (actually check for CR or LF as 199 * end of message). 200 * 201 * @return true if message has correct parity bit 202 */ 203 @Override 204 public boolean checkParity() { 205 //jmri.util.swing.JmriJOptionPane.showMessageDialog(null, "A-Programma komt tot hier!"); 206 int len = getNumDataElements(); 207 return (getElement(len - 1) == (0x0D | 0x0A)); 208 } 209 // programma komt hier volgens mij nooit 210 // in fact set CR as end of message 211 212 @Override 213 public void setParity() { 214 jmri.util.swing.JmriJOptionPane.showMessageDialog(null, "B-Programma komt tot hier!"); 215 int len = getNumDataElements(); 216 setElement(len - 1, 0x0D); 217 } 218 219 // decode messages of a particular form 220 public String getStringMsg() { 221 StringBuilder txt = new StringBuilder(); 222 if (protocol == Mx1Packetizer.BINARY) { 223 if (isCRCError()) { 224 txt.append(" === CRC ERROR === "); 225 } 226 if (getNumDataElements() <= 3) { 227 txt.append("Short Packet "); 228 return txt.toString(); 229 } 230 if ((getElement(1) & 0x10) == 0x10) { 231 txt.append("From PC"); 232 } else { 233 txt.append("From CS"); 234 } 235 txt.append(" Seq ").append(getElement(0) & 0xff); 236 if (getLongMessage()) { 237 txt.append(" (L)"); 238 } else { 239 txt.append(" (S)"); 240 } 241 int offset; 242 switch (getMessageType()) { 243 case PRIMARY: 244 txt.append(" Prim"); 245 break; 246 case ACKREP1: 247 txt.append(" Ack/Reply 1"); 248 break; 249 case REPLY2: 250 txt.append(" Reply 2"); 251 break; 252 case ACK2: 253 txt.append(" Ack 2"); 254 break; 255 default: 256 txt.append(" Unknown msg"); 257 break; 258 } 259 if (getModule() == MX1) { //was (getElement(1)&0x00) == 0x00 260 txt.append(" to/from CS (MX1)"); 261 switch (getPrimaryMessage()) { //was getElement(2) 262 case TRACKCTL: 263 offset = 0; 264 if (getMessageType() == ACKREP1) { 265 offset++; 266 } 267 txt.append(" Track Control "); 268 if ((getElement(3 + offset) & 0x03) == 0x03) { 269 txt.append(" Query Track Status "); 270 } else if ((getElement(3 + offset) & 0x01) == 0x01) { 271 txt.append(" Turn Track Off "); 272 } else if ((getElement(3 + offset) & 0x02) == 0x02) { 273 txt.append(" Turn Track On "); 274 } else { 275 txt.append(" Stop All Locos "); 276 } 277 break; 278 case 3: 279 txt.append(" Loco Control : "); 280 if (getMessageType() == PRIMARY) { 281 txt.append(getLocoAddress(getElement((3)), getElement(4))); 282 txt.append(((getElement(6) & 0x20) == 0x20) ? " Fwd " : " Rev "); 283 txt.append(((getElement(6) & 0x10) == 0x10) ? " F0: On " : " F0: Off "); 284 txt.append(decodeFunctionStates(getElement(7), getElement(8))); 285 } 286 break; 287 case 4: 288 txt.append(" Loco Funct "); 289 break; 290 case 5: 291 txt.append(" Loco Acc/Dec "); 292 break; 293 case 6: 294 txt.append(" Shuttle "); 295 break; 296 case 7: 297 txt.append(" Accessory "); 298 if (getMessageType() == PRIMARY) { 299 txt.append(getLocoAddress(getElement((3)), getElement(4))); 300 txt.append(((getElement(5) & 0x04) == 0x04) ? " Thrown " : " Closed "); 301 } 302 break; 303 case 8: 304 txt.append(" Loco Status "); 305 break; 306 case 9: 307 txt.append(" Acc Status "); 308 break; 309 case 10: 310 txt.append(" Address Control "); 311 break; 312 case 11: 313 txt.append(" CS State "); 314 break; 315 case 12: 316 txt.append(" Read/Write CS CV "); 317 break; 318 case 13: 319 txt.append(" CS Equip Query "); 320 break; 321 case 17: 322 txt.append(" Tool Type "); 323 break; 324 case PROGCMD: 325 offset = 0; 326 if (getMessageType() == ACKREP1) { 327 txt.append(" Prog CV "); 328 break; 329 } 330 if (getMessageType() == REPLY2) { 331 offset++; 332 } 333 if (getMessageType() == ACK2) { 334 txt.append("Ack to CS Message"); 335 break; 336 } 337 if (getNumDataElements() == 7 && getMessageType() == ACKREP1) { 338 txt.append(" Error Occured "); 339 txt.append(getErrorCode(getElement(6))); 340 txt.append(" Loco: ").append(getLocoAddress(getElement((3 + offset)), getElement(4 + offset))); 341 break; 342 } 343 /*if(getNumDataElements()<7){ 344 txt.append(" Ack L1 "); 345 break; 346 }*/ 347 if ((getMessageType() == PRIMARY && getNumDataElements() == 8)) { 348 txt.append(" Write CV "); 349 } else { 350 txt.append(" Read CV "); 351 } 352 txt.append("Loco: ").append(getLocoAddress(getElement((3 + offset)), getElement(4 + offset))); 353 if ((getElement(3 + offset) & 0x80) == 0x80) { 354 txt.append(" DCC"); 355 } 356 int cv = (((getElement(5 + offset) & 0xff) << 8) + (getElement(6 + offset) & 0xff)); 357 txt.append(" CV: ").append(cv); 358 if (getNumDataElements() >= (8 + offset)) { //Version 61.26 and later includes an extra error bit at the end of the packet 359 txt.append(" Set To: ").append(getElement(7 + offset) & 0xff); 360 } 361 break; 362 case 254: 363 txt.append(" Cur Acc Memory "); 364 break; 365 case 255: 366 txt.append(" Cur Loco Memory "); 367 break; 368 default: 369 txt.append(" Unknown "); 370 } 371 } else if ((getElement(1) & 0x01) == 0x01) { 372 txt.append(" to/from Accessory Mod (MX8)"); 373 } else if ((getElement(1) & 0x02) == 0x02) { 374 txt.append(" to/from Track Section (MX9)"); 375 } else { 376 txt.append(" unknown"); 377 } 378 } 379 //int type = getElement(2); 380 381 return txt.toString(); 382 } 383 384 private String decodeFunctionStates(int cData2, int cData3) { 385 StringBuilder txt = new StringBuilder(); 386 387 txt.append(((cData2 & 0x1) == 0x1) ? " F1: On " : " F1: Off "); 388 txt.append(((cData2 & 0x2) == 0x2) ? " F2: On " : " F2: Off "); 389 txt.append(((cData2 & 0x4) == 0x4) ? " F3: On " : " F3: Off "); 390 txt.append(((cData2 & 0x8) == 0x8) ? " F4: On " : " F4: Off "); 391 txt.append(((cData2 & 0x10) == 0x10) ? " F5: On " : " F5: Off "); 392 txt.append(((cData2 & 0x20) == 0x20) ? " F6: On " : " F6: Off "); 393 txt.append(((cData2 & 0x40) == 0x40) ? " F7: On " : " F7: Off "); 394 txt.append(((cData2 & 0x80) == 0x80) ? " F8: On " : " F8: Off "); 395 396 txt.append(((cData3 & 0x1) == 0x1) ? " F9: On " : " F9: Off "); 397 txt.append(((cData3 & 0x2) == 0x2) ? " F10: On " : " F10: Off "); 398 txt.append(((cData3 & 0x4) == 0x4) ? " F11: On " : " F11: Off "); 399 txt.append(((cData3 & 0x8) == 0x8) ? " F12: On " : " F12: Off "); 400 401 return txt.toString(); 402 } 403 404 public int getLocoAddress() { 405 int offset = 0; 406 if (getMessageType() == REPLY2) { 407 offset++; 408 } else if (getMessageType() == ACKREP1) { 409 offset = +2; 410 } 411 if (getNumDataElements() == (4 + offset)) { 412 return getLocoAddress(getElement(3 + offset), getElement(4 + offset)); 413 } 414 return -1; 415 } 416 417 public int getCvValue() { 418 int offset = 0; 419 if (getMessageType() == REPLY2) { 420 offset++; 421 } else if (getMessageType() == ACKREP1) { 422 offset = +2; 423 } 424 if (getNumDataElements() >= (8 + offset)) { //Version 61.26 and later includes an extra error bit at the end of the packet 425 return (getElement(7 + offset) & 0xff); 426 } 427 return -1; 428 } 429 430 int getLocoAddress(int hi, int lo) { 431 hi = hi & 0x3F; 432 if (hi == 0) { 433 return lo; 434 } else { 435 hi = (((hi & 255) - 192) << 8); 436 hi = hi + (lo & 255); 437 return hi; 438 } 439 } 440 441 String getErrorCode(int i) { 442 switch (i) { 443 case NO_ERROR: 444 return "No Error"; 445 case ERR_ADDRESS: 446 return "Invalid Address"; 447 case ERR_INDEX: 448 return "Invalid Index"; 449 case ERR_FORWARD: 450 return "Could not be Forwarded"; 451 case ERR_BUSY: 452 return "CMD Busy"; 453 case ERR_NO_MOT: 454 return "Motorola Jump Off"; 455 case ERR_NO_DCC: 456 return "DCC Jump Off"; 457 case ERR_CV_ADDRESS: 458 return "Invalid CV"; 459 case ERR_SECTION: 460 return "Invalid Section"; 461 case ERR_NO_MODUL: 462 return "No Module Found"; 463 case ERR_MESSAGE: 464 return "Error in Message"; 465 case ERR_SPEED: 466 return "Invalid Speed"; 467 default: 468 return "Unknown Error"; 469 } 470 } 471 472 final static int NO_ERROR = 0x00; 473 final static int ERR_ADDRESS = 0x01; 474 final static int ERR_INDEX = 0x02; 475 final static int ERR_FORWARD = 0x03; 476 final static int ERR_BUSY = 0x04; 477 final static int ERR_NO_MOT = 0x05; 478 final static int ERR_NO_DCC = 0x06; 479 final static int ERR_CV_ADDRESS = 0x07; 480 final static int ERR_SECTION = 0x08; 481 final static int ERR_NO_MODUL = 0x09; 482 final static int ERR_MESSAGE = 0x0a; 483 final static int ERR_SPEED = 0x0b; 484 485 final static int TRACKCTL = 0x02; 486 final static int PROGCMD = 0x13; 487 final static int LOCOCMD = 0x03; 488 final static int ACCCMD = 0x07; 489 490 static public Mx1Message getCmdStnDetails() { 491 Mx1Message m = new Mx1Message(4); 492 m.setElement(1, 0x10); 493 m.setElement(2, 0x13); 494 m.setElement(3, 0x00); 495 return m; 496 } 497 498 /** 499 * Set Track Power Off/Emergency Stop 500 * 501 * @return MrcMessage 502 */ 503 static public Mx1Message setPowerOff() { 504 Mx1Message m = new Mx1Message(4, Mx1Packetizer.BINARY); 505 m.setElement(1, 0x10); // PC control short message 506 507 m.setElement(2, TRACKCTL); 508 m.setElement(3, 0x01); 509 return m; 510 } 511 512 static public Mx1Message setPowerOn() { 513 Mx1Message m = new Mx1Message(4, Mx1Packetizer.BINARY); 514 m.setElement(1, 0x10); // PC control short message 515 516 m.setElement(2, TRACKCTL); 517 m.setElement(3, 0x02); 518 return m; 519 } 520 521 static public Mx1Message getTrackStatus() { 522 Mx1Message m = new Mx1Message(4, Mx1Packetizer.BINARY); 523 m.setElement(1, 0x10); // PC control short message 524 525 m.setElement(2, TRACKCTL); 526 m.setElement(3, 0x03); 527 return m; 528 } 529 530 /** 531 * Create a message to read or write a CV. 532 * 533 * @param locoAddress address of the loco 534 * @param cv CV to read or write 535 * @param value value to write to CV, if -1 CV is read 536 * @param dcc true if decoder is DCC; false if decoder is Motorola 537 * @return a message to read or write a CV 538 */ 539 // javadoc did indicate locoAddress could be blank to use programming track, but that's not possible with an int 540 static public Mx1Message getDecProgCmd(int locoAddress, int cv, int value, boolean dcc) { 541 Mx1Message m; 542 if (value == -1) { 543 m = new Mx1Message(7, Mx1Packetizer.BINARY); 544 } else { 545 m = new Mx1Message(8, Mx1Packetizer.BINARY); 546 } 547 m.setElement(0, 0x00); 548 m.setElement(1, 0x10); // PC control short message 549 550 m.setElement(2, PROGCMD); 551 int locoHi = locoAddress >> 8; 552 if (dcc) { 553 locoHi = locoHi + 128; 554 } else { 555 locoHi = locoHi + 64; 556 } 557 m.setElement(3, (locoHi)); 558 m.setElement(4, (locoAddress & 0xff)); 559 m.setElement(5, cv >> 8); 560 m.setElement(6, cv & 0xff); 561 if (value != -1) { 562 m.setElement(7, value); 563 } 564 return m; 565 } 566 567 /** 568 * Create a locomotive control message. 569 * 570 * @param locoAddress address of the loco 571 * @param speed Speed Step in the actual Speed Step System 572 * @param dcc true if decoder is DCC; false if decoder is Motorola 573 * @param cData1 ??? 574 * @param cData2 functions output 0-7 575 * @param cData3 functions output 9-12 576 * @return message controlling a locomotive 577 */ 578 static public Mx1Message getLocoControl(int locoAddress, int speed, boolean dcc, int cData1, int cData2, int cData3) { 579 Mx1Message m = new Mx1Message(9, Mx1Packetizer.BINARY); 580 m.setElement(0, 0x00); 581 m.setElement(1, 0x10); // PC control short message 582 583 m.setElement(2, LOCOCMD); 584 //High add 80 to indicate DCC 585 int locoHi = locoAddress >> 8; 586 if (dcc) { 587 locoHi = locoHi + 128; 588 } else { 589 locoHi = locoHi + 64; 590 } 591 m.setElement(3, (locoHi)); 592 m.setElement(4, (locoAddress & 0xff)); 593 m.setElement(5, speed); 594 m.setElement(6, cData1); 595 m.setElement(7, cData2); 596 m.setElement(8, cData3); 597 return m; 598 } 599 600 static public Mx1Message getSwitchMsg(int accAddress, int setting, boolean dcc) { 601 Mx1Message m = new Mx1Message(6, Mx1Packetizer.BINARY); 602 m.setElement(0, 0x00); 603 m.setElement(1, 0x10); // PC control short message 604 605 m.setElement(2, ACCCMD); 606 //High add 80 to indicate DCC 607 int accHi = accAddress >> 8; 608 if (dcc) { 609 accHi = accHi + 128; 610 } else { 611 accHi = accHi + 64; 612 } 613 m.setElement(3, (accHi)); 614 m.setElement(4, (accAddress & 0xff)); 615 m.setElement(5, 0x00); 616 if (setting == jmri.Turnout.THROWN) { 617 m.setElement(5, 0x04); 618 } 619 return m; 620 } 621 622 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Mx1Message.class); 623 624}