001package jmri.jmrix.lenz; 002 003import java.io.Serializable; 004import java.lang.reflect.Constructor; 005import java.lang.reflect.InvocationTargetException; 006import java.util.ArrayList; 007import java.util.List; 008import java.util.Set; 009 010import org.reflections.Reflections; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013import jmri.SpeedStepMode; 014 015/** 016 * Represents a single command or response on the XpressNet. 017 * <p> 018 * Content is represented with ints to avoid the problems with sign-extension 019 * that bytes have, and because a Java char is actually a variable number of 020 * bytes in Unicode. 021 * 022 * @author Bob Jacobsen Copyright (C) 2002 023 * @author Paul Bender Copyright (C) 2003-2010 024 * 025 */ 026public class XNetMessage extends jmri.jmrix.AbstractMRMessage implements Serializable { 027 028 private static int _nRetries = 5; 029 030 /* According to the specification, XpressNet has a maximum timing 031 interval of 500 milliseconds during normal communications */ 032 protected static final int XNetProgrammingTimeout = 10000; 033 private static int XNetMessageTimeout = 5000; 034 035 /** 036 * Create a new object, representing a specific-length message. 037 * 038 * @param len Total bytes in message, including opcode and error-detection 039 * byte. Valid values are 0 to 15 (0x0 to 0xF). 040 */ 041 public XNetMessage(int len) { 042 super(len); 043 if (len > 15 ) { // only check upper bound. Lower bound checked in 044 // super call. 045 log.error("Invalid length in ctor: {}", len); 046 throw new IllegalArgumentException("Invalid length in ctor: " + len); 047 } 048 setBinary(true); 049 setRetries(_nRetries); 050 setTimeout(XNetMessageTimeout); 051 _nDataChars = len; 052 } 053 054 /** 055 * Create a new object, that is a copy of an existing message. 056 * 057 * @param message an existing XpressNet message 058 */ 059 public XNetMessage(XNetMessage message) { 060 super(message); 061 setBinary(true); 062 setRetries(_nRetries); 063 setTimeout(XNetMessageTimeout); 064 } 065 066 /** 067 * Create an XNetMessage from an XNetReply. 068 * @param message existing XNetReply. 069 */ 070 public XNetMessage(XNetReply message) { 071 super(message.getNumDataElements()); 072 setBinary(true); 073 setRetries(_nRetries); 074 setTimeout(XNetMessageTimeout); 075 for (int i = 0; i < message.getNumDataElements(); i++) { 076 setElement(i, message.getElement(i)); 077 } 078 } 079 080 /** 081 * Create an XNetMessage from a String containing bytes. 082 * @param s string containing data bytes. 083 */ 084 public XNetMessage(String s) { 085 setBinary(true); 086 setRetries(_nRetries); 087 setTimeout(XNetMessageTimeout); 088 // gather bytes in result 089 byte[] b = jmri.util.StringUtil.bytesFromHexString(s); 090 if (b.length == 0) { 091 // no such thing as a zero-length message 092 _nDataChars = 0; 093 _dataChars = null; 094 return; 095 } 096 _nDataChars = b.length; 097 _dataChars = new int[_nDataChars]; 098 for (int i = 0; i < b.length; i++) { 099 setElement(i, b[i]); 100 } 101 } 102 103 // note that the opcode is part of the message, so we treat it 104 // directly 105 // WARNING: use this only with opcodes that have a variable number 106 // of arguments following included. Otherwise, just use setElement 107 @Override 108 public void setOpCode(int i) { 109 if (i > 0xF || i < 0) { 110 log.error("Opcode invalid: {}", i); 111 } 112 setElement(0, ((i * 16) & 0xF0) | ((getNumDataElements() - 2) & 0xF)); 113 } 114 115 @Override 116 public int getOpCode() { 117 return (getElement(0) / 16) & 0xF; 118 } 119 120 /** 121 * Get a String representation of the op code in hex. 122 * {@inheritDoc} 123 */ 124 @Override 125 public String getOpCodeHex() { 126 return "0x" + Integer.toHexString(getOpCode()); 127 } 128 129 /** 130 * Check whether the message has a valid parity. 131 * @return true if parity valid, else false. 132 */ 133 public boolean checkParity() { 134 int len = getNumDataElements(); 135 int chksum = 0x00; /* the seed */ 136 137 int loop; 138 139 for (loop = 0; loop < len - 1; loop++) { // calculate contents for data part 140 chksum ^= getElement(loop); 141 } 142 return ((chksum & 0xFF) == getElement(len - 1)); 143 } 144 145 public void setParity() { 146 int len = getNumDataElements(); 147 int chksum = 0x00; /* the seed */ 148 149 int loop; 150 151 for (loop = 0; loop < len - 1; loop++) { // calculate contents for data part 152 chksum ^= getElement(loop); 153 } 154 setElement(len - 1, chksum & 0xFF); 155 } 156 157 /** 158 * Get an integer representation of a BCD value. 159 * @param n message element index. 160 * @return integer of BCD. 161 */ 162 public Integer getElementBCD(int n) { 163 return Integer.decode(Integer.toHexString(getElement(n))); 164 } 165 166 /** 167 * Get the message length. 168 * @return message length. 169 */ 170 public int length() { 171 return _nDataChars; 172 } 173 174 /** 175 * Set the default number of retries for an XpressNet message. 176 * 177 * @param t number of retries to attempt 178 */ 179 public static void setXNetMessageRetries(int t) { 180 _nRetries = t; 181 } 182 183 /** 184 * Set the default timeout for an XpressNet message. 185 * 186 * @param t Timeout in milliseconds 187 */ 188 public static void setXNetMessageTimeout(int t) { 189 XNetMessageTimeout = t; 190 } 191 192 /** 193 * Most messages are sent with a reply expected, but 194 * we have a few that we treat as though the reply is always 195 * a broadcast message, because the reply usually comes to us 196 * that way. 197 * {@inheritDoc} 198 */ 199 @Override 200 public boolean replyExpected() { 201 return !broadcastReply; 202 } 203 204 private boolean broadcastReply = false; 205 206 /** 207 * Tell the traffic controller we expect this 208 * message to have a broadcast reply. 209 */ 210 public void setBroadcastReply() { 211 broadcastReply = true; 212 } 213 214 // decode messages of a particular form 215 // create messages of a particular form 216 217 /** 218 * Encapsulate an NMRA DCC packet in an XpressNet message. 219 * <p> 220 * On Current (v3.5) Lenz command stations, the Operations Mode 221 * Programming Request is implemented by sending a packet directly 222 * to the rails. This packet is not checked by the XpressNet 223 * protocol, and is just the track packet with an added header 224 * byte. 225 * <p> 226 * NOTE: Lenz does not say this will work for anything but 5 227 * byte packets. 228 * @param packet byte array containing packet data elements. 229 * @return message to send DCC packet. 230 */ 231 public static XNetMessage getNMRAXNetMsg(byte[] packet) { 232 XNetMessage msg = new XNetMessage(packet.length + 2); 233 msg.setOpCode((XNetConstants.OPS_MODE_PROG_REQ & 0xF0) >> 4); 234 msg.setElement(1, 0x30); 235 for (int i = 0; i < packet.length; i++) { 236 msg.setElement((i + 2), packet[i] & 0xff); 237 } 238 msg.setParity(); 239 return (msg); 240 } 241 242 /* 243 * The next group of routines are used by Feedback and/or turnout 244 * control code. These are used in multiple places within the code, 245 * so they appear here. 246 */ 247 248 /** 249 * Generate a message to change turnout state. 250 * @param pNumber address number. 251 * @param pClose true if set turnout closed. 252 * @param pThrow true if set turnout thrown. 253 * @param pOn accessory line true for on, false off. 254 * @return message containing turnout command. 255 */ 256 public static XNetMessage getTurnoutCommandMsg(int pNumber, boolean pClose, 257 boolean pThrow, boolean pOn) { 258 XNetMessage l = new XNetMessage(4); 259 l.setElement(0, XNetConstants.ACC_OPER_REQ); 260 261 // compute address byte fields 262 int hiadr = (pNumber - 1) / 4; 263 int loadr = ((pNumber - 1) - hiadr * 4) * 2; 264 // The MSB of the upper nibble is required to be set on 265 // The rest of the upper nibble should be zeros. 266 // The MSB of the lower nibble says weather or not the 267 // accessory line should be "on" or "off" 268 if (!pOn) { 269 loadr |= 0x80; 270 } else { 271 loadr |= 0x88; 272 } 273 // If we are sending a "throw" command, we set the LSB of the 274 // lower nibble on, otherwise, we leave it "off". 275 if (pThrow) { 276 loadr |= 0x01; 277 } 278 279 // we don't know how to command both states right now! 280 if (pClose && pThrow) { 281 log.error("XpressNet turnout logic can't handle both THROWN and CLOSED yet"); 282 } 283 // store and send 284 l.setElement(1, hiadr); 285 l.setElement(2, loadr); 286 l.setParity(); // Set the parity bit 287 288 return l; 289 } 290 291 /** 292 * Generate a message to receive the feedback information for an upper or 293 * lower nibble of the feedback address in question. 294 * @param pNumber feedback address. 295 * @param pLowerNibble true for upper nibble, else false for lower. 296 * @return feedback request message. 297 */ 298 public static XNetMessage getFeedbackRequestMsg(int pNumber, 299 boolean pLowerNibble) { 300 XNetMessage l = new XNetMessage(4); 301 l.setBroadcastReply(); // we the message reply as a broadcast message. 302 l.setElement(0, XNetConstants.ACC_INFO_REQ); 303 304 // compute address byte field 305 l.setElement(1, (pNumber - 1) / 4); 306 // The MSB of the upper nibble is required to be set on 307 // The rest of the upper nibble should be zeros. 308 // The LSB of the lower nibble says weather or not the 309 // information request is for the upper or lower nibble. 310 if (pLowerNibble) { 311 l.setElement(2, 0x80); 312 } else { 313 l.setElement(2, 0x81); 314 } 315 l.setParity(); // Set the parity bit 316 return l; 317 } 318 319 /* 320 * Next, we have some messages related to sending programming commands. 321 */ 322 323 public static XNetMessage getServiceModeResultsMsg() { 324 XNetMessage m = new XNetMessage(3); 325 m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE); 326 m.setTimeout(XNetProgrammingTimeout); 327 m.setElement(0, XNetConstants.CS_REQUEST); 328 m.setElement(1, XNetConstants.SERVICE_MODE_CSRESULT); 329 m.setParity(); // Set the parity bit 330 return m; 331 } 332 333 public static XNetMessage getExitProgModeMsg() { 334 XNetMessage m = new XNetMessage(3); 335 m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE); 336 m.setElement(0, XNetConstants.CS_REQUEST); 337 m.setElement(1, XNetConstants.RESUME_OPS); 338 m.setParity(); 339 return m; 340 } 341 342 public static XNetMessage getReadPagedCVMsg(int cv) { 343 XNetMessage m = new XNetMessage(4); 344 m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE); 345 m.setTimeout(XNetProgrammingTimeout); 346 m.setElement(0, XNetConstants.PROG_READ_REQUEST); 347 m.setElement(1, XNetConstants.PROG_READ_MODE_PAGED); 348 m.setElement(2, (0xff & cv)); 349 m.setParity(); // Set the parity bit 350 return m; 351 } 352 353 public static XNetMessage getReadDirectCVMsg(int cv) { 354 XNetMessage m = new XNetMessage(4); 355 m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE); 356 m.setTimeout(XNetProgrammingTimeout); 357 m.setElement(0, XNetConstants.PROG_READ_REQUEST); 358 if (cv < 0x0100) /* Use the version 3.5 command for CVs <= 256 */ { 359 m.setElement(1, XNetConstants.PROG_READ_MODE_CV); 360 } else if (cv == 0x0400) /* For CV1024, we need to send the version 3.6 361 command for CVs 1 to 256, sending a 0 for the 362 CV */ { 363 m.setElement(1, XNetConstants.PROG_READ_MODE_CV_V36); 364 } else /* and the version 3.6 command for CVs > 256 */ { 365 m.setElement(1, XNetConstants.PROG_READ_MODE_CV_V36 | ((cv & 0x0300) >> 8)); 366 } 367 m.setElement(2, (0xff & cv)); 368 m.setParity(); // Set the parity bit 369 return m; 370 } 371 372 public static XNetMessage getWritePagedCVMsg(int cv, int val) { 373 XNetMessage m = new XNetMessage(5); 374 m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE); 375 m.setTimeout(XNetProgrammingTimeout); 376 m.setElement(0, XNetConstants.PROG_WRITE_REQUEST); 377 m.setElement(1, XNetConstants.PROG_WRITE_MODE_PAGED); 378 m.setElement(2, (0xff & cv)); 379 m.setElement(3, val); 380 m.setParity(); // Set the parity bit 381 return m; 382 } 383 384 public static XNetMessage getWriteDirectCVMsg(int cv, int val) { 385 XNetMessage m = new XNetMessage(5); 386 m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE); 387 m.setTimeout(XNetProgrammingTimeout); 388 m.setElement(0, XNetConstants.PROG_WRITE_REQUEST); 389 if (cv < 0x0100) /* Use the version 3.5 command for CVs <= 256 */ { 390 m.setElement(1, XNetConstants.PROG_WRITE_MODE_CV); 391 } else if (cv == 0x0400) /* For CV1024, we need to send the version 3.6 392 command for CVs 1 to 256, sending a 0 for the 393 CV */ { 394 m.setElement(1, XNetConstants.PROG_WRITE_MODE_CV_V36); 395 } else /* and the version 3.6 command for CVs > 256 */ { 396 m.setElement(1, XNetConstants.PROG_WRITE_MODE_CV_V36 | ((cv & 0x0300) >> 8)); 397 } 398 m.setElement(2, (0xff & cv)); 399 m.setElement(3, val); 400 m.setParity(); // Set the parity bit 401 return m; 402 } 403 404 public static XNetMessage getReadRegisterMsg(int reg) { 405 if (reg > 8) { 406 log.error("register number too large: {}",reg); 407 } 408 XNetMessage m = new XNetMessage(4); 409 m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE); 410 m.setTimeout(XNetProgrammingTimeout); 411 m.setElement(0, XNetConstants.PROG_READ_REQUEST); 412 m.setElement(1, XNetConstants.PROG_READ_MODE_REGISTER); 413 m.setElement(2, (0x0f & reg)); 414 m.setParity(); // Set the parity bit 415 return m; 416 } 417 418 public static XNetMessage getWriteRegisterMsg(int reg, int val) { 419 if (reg > 8) { 420 log.error("register number too large: {}",reg); 421 } 422 XNetMessage m = new XNetMessage(5); 423 m.setNeededMode(jmri.jmrix.AbstractMRTrafficController.PROGRAMINGMODE); 424 m.setTimeout(XNetProgrammingTimeout); 425 m.setElement(0, XNetConstants.PROG_WRITE_REQUEST); 426 m.setElement(1, XNetConstants.PROG_WRITE_MODE_REGISTER); 427 m.setElement(2, (0x0f & reg)); 428 m.setElement(3, val); 429 m.setParity(); // Set the parity bit 430 return m; 431 } 432 433 public static XNetMessage getWriteOpsModeCVMsg(int AH, int AL, int cv, int val) { 434 XNetMessage m = new XNetMessage(8); 435 m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ); 436 m.setElement(1, XNetConstants.OPS_MODE_PROG_WRITE_REQ); 437 m.setElement(2, AH); 438 m.setElement(3, AL); 439 /* Element 4 is 0xEC + the upper two bits of the 10 bit CV address. 440 NOTE: This is the track packet CV, not the human readable CV, so 441 its value actually is one less than what we normally think of it as.*/ 442 int temp = (cv - 1) & 0x0300; 443 temp = temp / 0x00FF; 444 m.setElement(4, 0xEC + temp); 445 /* Element 5 is the lower 8 bits of the cv */ 446 m.setElement(5, ((0x00ff & cv) - 1)); 447 m.setElement(6, val); 448 m.setParity(); // Set the parity bit 449 return m; 450 } 451 452 public static XNetMessage getVerifyOpsModeCVMsg(int AH, int AL, int cv, int val) { 453 XNetMessage m = new XNetMessage(8); 454 m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ); 455 m.setElement(1, XNetConstants.OPS_MODE_PROG_WRITE_REQ); 456 m.setElement(2, AH); 457 m.setElement(3, AL); 458 /* Element 4 is 0xE4 + the upper two bits of the 10 bit CV address. 459 NOTE: This is the track packet CV, not the human readable CV, so 460 its value actually is one less than what we normally think of it as.*/ 461 int temp = (cv - 1) & 0x0300; 462 temp = temp / 0x00FF; 463 m.setElement(4, 0xE4 + temp); 464 /* Element 5 is the lower 8 bits of the cv */ 465 m.setElement(5, ((0x00ff & cv) - 1)); 466 m.setElement(6, val); 467 m.setParity(); // Set the parity bit 468 return m; 469 } 470 471 public static XNetMessage getBitWriteOpsModeCVMsg(int AH, int AL, int cv, int bit, boolean value) { 472 XNetMessage m = new XNetMessage(8); 473 m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ); 474 m.setElement(1, XNetConstants.OPS_MODE_PROG_WRITE_REQ); 475 m.setElement(2, AH); 476 m.setElement(3, AL); 477 /* Element 4 is 0xE8 + the upper two bits of the 10 bit CV address. 478 NOTE: This is the track packet CV, not the human readable CV, so 479 its value actually is one less than what we normally think of it as.*/ 480 int temp = (cv - 1) & 0x0300; 481 temp = temp / 0x00FF; 482 m.setElement(4, 0xE8 + temp); 483 /* Element 5 is the lower 8 bits of the cv */ 484 m.setElement(5, ((0x00ff & cv) - 1)); 485 /* Since this is a bit write, Element 6 is: 486 0xE0 + 487 bit 3 is the value to write 488 bit's 0-2 are the location of the bit we are changing */ 489 if (value) { 490 m.setElement(6, ((0xe8) | (bit & 0xff))); 491 } else // value == false 492 { 493 m.setElement(6, ((0xe0) | (bit & 0xff))); 494 } 495 m.setParity(); // Set the parity bit 496 return m; 497 } 498 499 public static XNetMessage getBitVerifyOpsModeCVMsg(int AH, int AL, int cv, int bit, boolean value) { 500 XNetMessage m = new XNetMessage(8); 501 m.setElement(0, XNetConstants.OPS_MODE_PROG_REQ); 502 m.setElement(1, XNetConstants.OPS_MODE_PROG_WRITE_REQ); 503 m.setElement(2, AH); 504 m.setElement(3, AL); 505 /* Element 4 is 0xE8 + the upper two bits of the 10 bit CV address. 506 NOTE: This is the track packet CV, not the human readable CV, so 507 its value actually is one less than what we normally think of it as.*/ 508 int temp = (cv - 1) & 0x0300; 509 temp = temp / 0x00FF; 510 m.setElement(4, 0xE8 + temp); 511 /* Element 5 is the lower 8 bits of the cv */ 512 m.setElement(5, ((0x00ff & cv) - 1)); 513 /* Since this is a bit verify, Element 6 is: 514 0xF0 + 515 bit 3 is the value to write 516 bit's 0-2 are the location of the bit we are changing */ 517 if (value) { 518 m.setElement(6, ((0xf8) | (bit & 0xff))); 519 } else // value == false 520 { 521 m.setElement(6, ((0xf0) | (bit & 0xff))); 522 } 523 m.setParity(); // Set the parity bit 524 return m; 525 } 526 527 /* 528 * Next, we have routines to generate XpressNet Messages for building 529 * and tearing down a consist or a double header. 530 */ 531 532 /** 533 * Build a Double Header. 534 * 535 * @param address1 the first address in the consist 536 * @param address2 the second address in the consist. 537 * @return message to build double header. 538 */ 539 public static XNetMessage getBuildDoubleHeaderMsg(int address1, int address2) { 540 XNetMessage msg = new XNetMessage(7); 541 msg.setElement(0, XNetConstants.LOCO_DOUBLEHEAD); 542 msg.setElement(1, XNetConstants.LOCO_DOUBLEHEAD_BYTE2); 543 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address1)); 544 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address1)); 545 msg.setElement(4, LenzCommandStation.getDCCAddressHigh(address2)); 546 msg.setElement(5, LenzCommandStation.getDCCAddressLow(address2)); 547 msg.setParity(); 548 return (msg); 549 } 550 551 /** 552 * Dissolve a Double Header. 553 * 554 * @param address one of the two addresses in the Double Header 555 * @return message to dissolve a double header. 556 */ 557 public static XNetMessage getDisolveDoubleHeaderMsg(int address) { 558 // All we have to do is call getBuildDoubleHeaderMsg with the 559 // second address as a zero 560 return (getBuildDoubleHeaderMsg(address, 0)); 561 } 562 563 /** 564 * Add a Single address to a specified Advanced consist. 565 * 566 * @param consist the consist address (1-99) 567 * @param address the locomotive address to add. 568 * @param isNormalDir tells us if the locomotive is going forward when 569 * the consist is going forward. 570 * @return message to add address to consist. 571 */ 572 public static XNetMessage getAddLocoToConsistMsg(int consist, int address, 573 boolean isNormalDir) { 574 XNetMessage msg = new XNetMessage(6); 575 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 576 if (isNormalDir) { 577 msg.setElement(1, XNetConstants.LOCO_ADD_MULTI_UNIT_REQ); 578 } else { 579 msg.setElement(1, XNetConstants.LOCO_ADD_MULTI_UNIT_REQ | 0x01); 580 } 581 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 582 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 583 msg.setElement(4, consist); 584 msg.setParity(); 585 return (msg); 586 } 587 588 /** 589 * Remove a Single address to a specified Advanced consist. 590 * 591 * @param consist the consist address (1-99) 592 * @param address the locomotive address to remove 593 * @return message to remove single address from consist. 594 */ 595 public static XNetMessage getRemoveLocoFromConsistMsg(int consist, int address) { 596 XNetMessage msg = new XNetMessage(6); 597 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 598 msg.setElement(1, XNetConstants.LOCO_REM_MULTI_UNIT_REQ); 599 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 600 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 601 msg.setElement(4, consist); 602 msg.setParity(); 603 return (msg); 604 } 605 606 607 /* 608 * Next, we have routines to generate XpressNet Messages for search 609 * and manipulation of the Command Station Database 610 */ 611 612 /** 613 * Given a locomotive address, search the database for the next 614 * member. 615 * (if the Address is zero start at the beginning of the database). 616 * 617 * @param address is the locomotive address 618 * @param searchForward indicates to search the database Forward if 619 * true, or backwards if False 620 * @return message to request next address. 621 */ 622 public static XNetMessage getNextAddressOnStackMsg(int address, boolean searchForward) { 623 XNetMessage msg = new XNetMessage(5); 624 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 625 if (searchForward) { 626 msg.setElement(1, XNetConstants.LOCO_STACK_SEARCH_FWD); 627 } else { 628 msg.setElement(1, XNetConstants.LOCO_STACK_SEARCH_BKWD); 629 } 630 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 631 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 632 msg.setParity(); 633 return (msg); 634 } 635 636 /** 637 * Given a consist address, search the database for the next Consist 638 * address. 639 * 640 * @param address is the consist address (in the range 1-99). 641 * If the Address is zero start at the beginning of the database. 642 * @param searchForward indicates to search the database Forward if 643 * true, or backwards if false 644 * @return message to get next consist address. 645 */ 646 public static XNetMessage getDBSearchMsgConsistAddress(int address, boolean searchForward) { 647 XNetMessage msg = new XNetMessage(4); 648 msg.setElement(0, XNetConstants.CS_MULTI_UNIT_REQ); 649 if (searchForward) { 650 msg.setElement(1, XNetConstants.CS_MULTI_UNIT_REQ_FWD); 651 } else { 652 msg.setElement(1, XNetConstants.CS_MULTI_UNIT_REQ_BKWD); 653 } 654 msg.setElement(2, address); 655 msg.setParity(); 656 return (msg); 657 } 658 659 /** 660 * Given a consist and a locomotive address, search the database for 661 * the next Locomotive in the consist. 662 * 663 * @param consist the consist address (1-99). 664 * If the Consist Address is zero start at the begining of the database 665 * @param address the locomotive address. 666 * If the Address is zero start at the begining of the consist 667 * @param searchForward indicates to search the database Forward if 668 * true, or backwards if False 669 * @return message to request next loco in consist. 670 */ 671 public static XNetMessage getDBSearchMsgNextMULoco(int consist, int address, boolean searchForward) { 672 XNetMessage msg = new XNetMessage(6); 673 msg.setElement(0, XNetConstants.LOCO_IN_MULTI_UNIT_SEARCH_REQ); 674 if (searchForward) { 675 msg.setElement(1, XNetConstants.LOCO_IN_MULTI_UNIT_REQ_FORWARD); 676 } else { 677 msg.setElement(1, XNetConstants.LOCO_IN_MULTI_UNIT_REQ_BACKWARD); 678 } 679 msg.setElement(2, consist); 680 msg.setElement(3, LenzCommandStation.getDCCAddressHigh(address)); 681 msg.setElement(4, LenzCommandStation.getDCCAddressLow(address)); 682 msg.setParity(); 683 return (msg); 684 } 685 686 /** 687 * Given a locomotive address, delete it from the database . 688 * 689 * @param address the locomotive address 690 * @return message to delete loco address from stack. 691 */ 692 public static XNetMessage getDeleteAddressOnStackMsg(int address) { 693 XNetMessage msg = new XNetMessage(5); 694 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 695 msg.setElement(1, XNetConstants.LOCO_STACK_DELETE); 696 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 697 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 698 msg.setParity(); 699 return (msg); 700 } 701 702 /** 703 * Given a locomotive address, request its status . 704 * 705 * @param address the locomotive address 706 * @return message to request loco status. 707 */ 708 public static XNetMessage getLocomotiveInfoRequestMsg(int address) { 709 XNetMessage msg = new XNetMessage(5); 710 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 711 msg.setElement(1, XNetConstants.LOCO_INFO_REQ_V3); 712 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 713 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 714 msg.setParity(); 715 return (msg); 716 } 717 718 /** 719 * Given a locomotive address, request the function state (momentary status). 720 * 721 * @param address the locomotive address 722 * @return momentary function state request request. 723 */ 724 public static XNetMessage getLocomotiveFunctionStatusMsg(int address) { 725 XNetMessage msg = new XNetMessage(5); 726 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 727 msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC); 728 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 729 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 730 msg.setParity(); 731 return (msg); 732 } 733 734 /** 735 * Given a locomotive address, request the function on/off state 736 * for functions 13-28 737 * 738 * @param address the locomotive address 739 * @return function state request request f13-f28. 740 */ 741 public static XNetMessage getLocomotiveFunctionHighOnStatusMsg(int address) { 742 XNetMessage msg = new XNetMessage(5); 743 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 744 msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC_HI_ON); 745 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 746 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 747 msg.setParity(); 748 return (msg); 749 } 750 751 /** 752 * Given a locomotive address, request the function state (momentary status) 753 * for high functions (functions 13-28). 754 * 755 * @param address the locomotive address 756 * @return momentary function state request request f13-f28. 757 */ 758 public static XNetMessage getLocomotiveFunctionHighMomStatusMsg(int address) { 759 XNetMessage msg = new XNetMessage(5); 760 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 761 msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC_HI_MOM); 762 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 763 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 764 msg.setParity(); 765 return (msg); 766 } 767 768 /* 769 * Generate an emergency stop for the specified address. 770 * 771 * @param address the locomotive address 772 */ 773 public static XNetMessage getAddressedEmergencyStop(int address) { 774 XNetMessage msg = new XNetMessage(4); 775 msg.setElement(0, XNetConstants.EMERGENCY_STOP); 776 msg.setElement(1, LenzCommandStation.getDCCAddressHigh(address)); 777 // set to the upper 778 // byte of the DCC address 779 msg.setElement(2, LenzCommandStation.getDCCAddressLow(address)); 780 // set to the lower byte 781 //of the DCC address 782 msg.setParity(); // Set the parity bit 783 return msg; 784 } 785 786 /** 787 * Generate a Speed and Direction Request message. 788 * 789 * @param address the locomotive address 790 * @param speedStepMode the speedstep mode see @jmri.DccThrottle 791 * for possible values. 792 * @param speed a normalized speed value (a floating point number between 0 793 * and 1). A negative value indicates emergency stop. 794 * @param isForward true for forward, false for reverse. 795 * @return set speed and direction message. 796 */ 797 public static XNetMessage getSpeedAndDirectionMsg(int address, 798 SpeedStepMode speedStepMode, 799 float speed, 800 boolean isForward) { 801 XNetMessage msg = new XNetMessage(6); 802 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 803 int element4value = 0; /* this is for holding the speed and 804 direction setting */ 805 806 if (speedStepMode == SpeedStepMode.NMRA_DCC_128) { 807 // We're in 128 speed step mode 808 msg.setElement(1, XNetConstants.LOCO_SPEED_128); 809 // Now, we need to figure out what to send in element 4 810 // Remember, the speed steps are identified as 0-127 (in 811 // 128 step mode), not 1-128. 812 int speedVal = java.lang.Math.round(speed * 126); 813 // speed step 1 is reserved to indicate emergency stop, 814 // so we need to step over speed step 1 815 if (speedVal >= 1) { 816 element4value = speedVal + 1; 817 } 818 } else if (speedStepMode == SpeedStepMode.NMRA_DCC_28) { 819 // We're in 28 speed step mode 820 msg.setElement(1, XNetConstants.LOCO_SPEED_28); 821 // Now, we need to figure out what to send in element 4 822 int speedVal = java.lang.Math.round(speed * 28); 823 // The first speed step used is actually at 4 for 28 824 // speed step mode. 825 if (speedVal >= 1) { 826 speedVal += 3; 827 } 828 // We have to re-arange the bits, since bit 4 is the LSB, 829 // but other bits are in order from 0-3 830 element4value = ((speedVal & 0x1e) >> 1) 831 + ((speedVal & 0x01) << 4); 832 } else if (speedStepMode == SpeedStepMode.NMRA_DCC_27) { 833 // We're in 27 speed step mode 834 msg.setElement(1, XNetConstants.LOCO_SPEED_27); 835 // Now, we need to figure out what to send in element 4 836 int speedVal = java.lang.Math.round(speed * 27); 837 // The first speed step used is actually at 4 for 27 838 // speed step mode. 839 if (speedVal >= 1) { 840 speedVal += 3; 841 } 842 // We have to re-arange the bits, since bit 4 is the LSB, 843 // but other bits are in order from 0-3 844 element4value = ((speedVal & 0x1e) >> 1) 845 + ((speedVal & 0x01) << 4); 846 } else { 847 // We're in 14 speed step mode 848 msg.setElement(1, XNetConstants.LOCO_SPEED_14); 849 // Now, we need to figure out what to send in element 4 850 element4value = (int) (speed * 14); 851 int speedVal = java.lang.Math.round(speed * 14); 852 // The first speed step used is actually at 2 for 14 853 // speed step mode. 854 if (speedVal >= 1) { 855 element4value += 1; 856 } 857 } 858 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 859 // set to the upper byte of the DCC address 860 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 861 // set to the lower byte 862 //of the DCC address 863 if (isForward) { 864 /* the direction bit is always the most significant bit */ 865 element4value += 128; 866 } 867 msg.setElement(4, element4value); 868 msg.setParity(); // Set the parity bit 869 return msg; 870 } 871 872 /** 873 * Generate a Function Group One Operation Request message. 874 * 875 * @param address the locomotive address 876 * @param f0 is true if f0 is on, false if f0 is off 877 * @param f1 is true if f1 is on, false if f1 is off 878 * @param f2 is true if f2 is on, false if f2 is off 879 * @param f3 is true if f3 is on, false if f3 is off 880 * @param f4 is true if f4 is on, false if f4 is off 881 * @return set function group 1 message. 882 */ 883 public static XNetMessage getFunctionGroup1OpsMsg(int address, 884 boolean f0, 885 boolean f1, 886 boolean f2, 887 boolean f3, 888 boolean f4) { 889 XNetMessage msg = new XNetMessage(6); 890 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 891 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP1); 892 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 893 // set to the upper byte of the DCC address 894 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 895 // set to the lower byte of the DCC address 896 // Now, we need to figure out what to send in element 3 897 int element4value = 0; 898 if (f0) { 899 element4value += 16; 900 } 901 if (f1) { 902 element4value += 1; 903 } 904 if (f2) { 905 element4value += 2; 906 } 907 if (f3) { 908 element4value += 4; 909 } 910 if (f4) { 911 element4value += 8; 912 } 913 msg.setElement(4, element4value); 914 msg.setParity(); // Set the parity bit 915 return msg; 916 } 917 918 /** 919 * Generate a Function Group One Set Momentary Functions message. 920 * 921 * @param address the locomotive address 922 * @param f0 is true if f0 is momentary 923 * @param f1 is true if f1 is momentary 924 * @param f2 is true if f2 is momentary 925 * @param f3 is true if f3 is momentary 926 * @param f4 is true if f4 is momentary 927 * @return set momentary function group 1 message. 928 */ 929 public static XNetMessage getFunctionGroup1SetMomMsg(int address, 930 boolean f0, 931 boolean f1, 932 boolean f2, 933 boolean f3, 934 boolean f4) { 935 XNetMessage msg = new XNetMessage(6); 936 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 937 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP1_MOMENTARY); 938 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 939 // set to the upper byte of the DCC address 940 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 941 // set to the lower byte of the DCC address 942 // Now, we need to figure out what to send in element 3 943 int element4value = 0; 944 if (f0) { 945 element4value += 16; 946 } 947 if (f1) { 948 element4value += 1; 949 } 950 if (f2) { 951 element4value += 2; 952 } 953 if (f3) { 954 element4value += 4; 955 } 956 if (f4) { 957 element4value += 8; 958 } 959 msg.setElement(4, element4value); 960 msg.setParity(); // Set the parity bit 961 return msg; 962 } 963 964 /** 965 * Generate a Function Group Two Operation Request message. 966 * 967 * @param address the locomotive address 968 * @param f5 is true if f5 is on, false if f5 is off 969 * @param f6 is true if f6 is on, false if f6 is off 970 * @param f7 is true if f7 is on, false if f7 is off 971 * @param f8 is true if f8 is on, false if f8 is off 972 * @return set function group 2 message. 973 */ 974 public static XNetMessage getFunctionGroup2OpsMsg(int address, 975 boolean f5, 976 boolean f6, 977 boolean f7, 978 boolean f8) { 979 XNetMessage msg = new XNetMessage(6); 980 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 981 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP2); 982 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 983 // set to the upper byte of the DCC address 984 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 985 // set to the lower byte of the DCC address 986 // Now, we need to figure out what to send in element 3 987 int element4value = 0; 988 if (f5) { 989 element4value += 1; 990 } 991 if (f6) { 992 element4value += 2; 993 } 994 if (f7) { 995 element4value += 4; 996 } 997 if (f8) { 998 element4value += 8; 999 } 1000 msg.setElement(4, element4value); 1001 msg.setParity(); // Set the parity bit 1002 return msg; 1003 } 1004 1005 /** 1006 * Generate a Function Group Two Set Momentary Functions message. 1007 * 1008 * @param address the locomotive address 1009 * @param f5 is true if f5 is momentary 1010 * @param f6 is true if f6 is momentary 1011 * @param f7 is true if f7 is momentary 1012 * @param f8 is true if f8 is momentary 1013 * @return set momentary function group 2 message. 1014 */ 1015 public static XNetMessage getFunctionGroup2SetMomMsg(int address, 1016 boolean f5, 1017 boolean f6, 1018 boolean f7, 1019 boolean f8) { 1020 XNetMessage msg = new XNetMessage(6); 1021 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 1022 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP2_MOMENTARY); 1023 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 1024 // set to the upper byte of the DCC address 1025 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 1026 // set to the lower byte of the DCC address 1027 // Now, we need to figure out what to send in element 3 1028 int element4value = 0; 1029 if (f5) { 1030 element4value += 1; 1031 } 1032 if (f6) { 1033 element4value += 2; 1034 } 1035 if (f7) { 1036 element4value += 4; 1037 } 1038 if (f8) { 1039 element4value += 8; 1040 } 1041 msg.setElement(4, element4value); 1042 msg.setParity(); // Set the parity bit 1043 return msg; 1044 } 1045 1046 /** 1047 * Generate a Function Group Three Operation Request message. 1048 * 1049 * @param address the locomotive address 1050 * @param f9 is true if f9 is on, false if f9 is off 1051 * @param f10 is true if f10 is on, false if f10 is off 1052 * @param f11 is true if f11 is on, false if f11 is off 1053 * @param f12 is true if f12 is on, false if f12 is off 1054 * @return set function group 3 message. 1055 */ 1056 public static XNetMessage getFunctionGroup3OpsMsg(int address, 1057 boolean f9, 1058 boolean f10, 1059 boolean f11, 1060 boolean f12) { 1061 XNetMessage msg = new XNetMessage(6); 1062 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 1063 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP3); 1064 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 1065 // set to the upper byte of the DCC address 1066 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 1067 // set to the lower byte of the DCC address 1068 // Now, we need to figure out what to send in element 3 1069 int element4value = 0; 1070 if (f9) { 1071 element4value += 1; 1072 } 1073 if (f10) { 1074 element4value += 2; 1075 } 1076 if (f11) { 1077 element4value += 4; 1078 } 1079 if (f12) { 1080 element4value += 8; 1081 } 1082 msg.setElement(4, element4value); 1083 msg.setParity(); // Set the parity bit 1084 return msg; 1085 } 1086 1087 /** 1088 * Generate a Function Group Three Set Momentary Functions message. 1089 * 1090 * @param address the locomotive address 1091 * @param f9 is true if f9 is momentary 1092 * @param f10 is true if f10 is momentary 1093 * @param f11 is true if f11 is momentary 1094 * @param f12 is true if f12 is momentary 1095 * @return set momentary function group 3 message. 1096 */ 1097 public static XNetMessage getFunctionGroup3SetMomMsg(int address, 1098 boolean f9, 1099 boolean f10, 1100 boolean f11, 1101 boolean f12) { 1102 XNetMessage msg = new XNetMessage(6); 1103 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 1104 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP3_MOMENTARY); 1105 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 1106 // set to the upper byte of the DCC address 1107 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 1108 // set to the lower byte of the DCC address 1109 // Now, we need to figure out what to send in element 3 1110 int element4value = 0; 1111 if (f9) { 1112 element4value += 1; 1113 } 1114 if (f10) { 1115 element4value += 2; 1116 } 1117 if (f11) { 1118 element4value += 4; 1119 } 1120 if (f12) { 1121 element4value += 8; 1122 } 1123 msg.setElement(4, element4value); 1124 msg.setParity(); // Set the parity bit 1125 return msg; 1126 } 1127 1128 /** 1129 * Generate a Function Group Four Operation Request message. 1130 * 1131 * @param address the locomotive address 1132 * @param f13 is true if f13 is on, false if f13 is off 1133 * @param f14 is true if f14 is on, false if f14 is off 1134 * @param f15 is true if f15 is on, false if f15 is off 1135 * @param f16 is true if f18 is on, false if f16 is off 1136 * @param f17 is true if f17 is on, false if f17 is off 1137 * @param f18 is true if f18 is on, false if f18 is off 1138 * @param f19 is true if f19 is on, false if f19 is off 1139 * @param f20 is true if f20 is on, false if f20 is off 1140 * @return set function group 4 message. 1141 */ 1142 public static XNetMessage getFunctionGroup4OpsMsg(int address, 1143 boolean f13, 1144 boolean f14, 1145 boolean f15, 1146 boolean f16, 1147 boolean f17, 1148 boolean f18, 1149 boolean f19, 1150 boolean f20) { 1151 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP4, 1152 f13, f14, f15, f16, f17, f18, f19, f20); 1153 } 1154 1155 /** 1156 * Generate a Function Group Four Set Momentary Function message. 1157 * 1158 * @param address the locomotive address 1159 * @param f13 is true if f13 is Momentary 1160 * @param f14 is true if f14 is Momentary 1161 * @param f15 is true if f15 is Momentary 1162 * @param f16 is true if f18 is Momentary 1163 * @param f17 is true if f17 is Momentary 1164 * @param f18 is true if f18 is Momentary 1165 * @param f19 is true if f19 is Momentary 1166 * @param f20 is true if f20 is Momentary 1167 * @return set momentary function group 4 message. 1168 */ 1169 public static XNetMessage getFunctionGroup4SetMomMsg(int address, 1170 boolean f13, 1171 boolean f14, 1172 boolean f15, 1173 boolean f16, 1174 boolean f17, 1175 boolean f18, 1176 boolean f19, 1177 boolean f20) { 1178 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP4_MOMENTARY, 1179 f13, f14, f15, f16, f17, f18, f19, f20); 1180 } 1181 1182 /** 1183 * Generate a Function Group Five Operation Request message. 1184 * 1185 * @param address the locomotive address 1186 * @param f21 is true if f21 is on, false if f21 is off 1187 * @param f22 is true if f22 is on, false if f22 is off 1188 * @param f23 is true if f23 is on, false if f23 is off 1189 * @param f24 is true if f24 is on, false if f24 is off 1190 * @param f25 is true if f25 is on, false if f25 is off 1191 * @param f26 is true if f26 is on, false if f26 is off 1192 * @param f27 is true if f27 is on, false if f27 is off 1193 * @param f28 is true if f28 is on, false if f28 is off 1194 * @return set function group 5 message. 1195 */ 1196 public static XNetMessage getFunctionGroup5OpsMsg(int address, 1197 boolean f21, 1198 boolean f22, 1199 boolean f23, 1200 boolean f24, 1201 boolean f25, 1202 boolean f26, 1203 boolean f27, 1204 boolean f28) { 1205 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP5, 1206 f21, f22, f23, f24, f25, f26, f27, f28); 1207 } 1208 1209 /** 1210 * Generate a Function Group Five Set Momentary Function message. 1211 * 1212 * @param address the locomotive address 1213 * @param f21 is true if f21 is momentary 1214 * @param f22 is true if f22 is momentary 1215 * @param f23 is true if f23 is momentary 1216 * @param f24 is true if f24 is momentary 1217 * @param f25 is true if f25 is momentary 1218 * @param f26 is true if f26 is momentary 1219 * @param f27 is true if f27 is momentary 1220 * @param f28 is true if f28 is momentary 1221 * @return set momentary function group 5 message. 1222 */ 1223 public static XNetMessage getFunctionGroup5SetMomMsg(int address, 1224 boolean f21, 1225 boolean f22, 1226 boolean f23, 1227 boolean f24, 1228 boolean f25, 1229 boolean f26, 1230 boolean f27, 1231 boolean f28) { 1232 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP5_MOMENTARY, 1233 f21, f22, f23, f24, f25, f26, f27, f28); 1234 } 1235 1236 // Generate a Function Group Operation Request message for some specific case. 1237 private static XNetMessage getFunctionGroupNOpsMsg(int address, int byte1, 1238 boolean fA, 1239 boolean fB, 1240 boolean fC, 1241 boolean fD, 1242 boolean fE, 1243 boolean fF, 1244 boolean fG, 1245 boolean fH) { 1246 XNetMessage msg = new XNetMessage(6); 1247 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 1248 msg.setElement(1, byte1); 1249 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 1250 // set to the upper byte of the DCC address 1251 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 1252 // set to the lower byte of the DCC address 1253 // Now, we need to figure out what to send in element 3 1254 int element4value = 0; 1255 if (fA) { 1256 element4value += 1; 1257 } 1258 if (fB) { 1259 element4value += 2; 1260 } 1261 if (fC) { 1262 element4value += 4; 1263 } 1264 if (fD) { 1265 element4value += 8; 1266 } 1267 if (fE) { 1268 element4value += 16; 1269 } 1270 if (fF) { 1271 element4value += 32; 1272 } 1273 if (fG) { 1274 element4value += 64; 1275 } 1276 if (fH) { 1277 element4value += 128; 1278 } 1279 msg.setElement(4, element4value); 1280 msg.setParity(); // Set the parity bit 1281 return msg; 1282 } 1283 1284 public static XNetMessage getFunctionGroup6OpsMsg(int address, 1285 boolean fA, boolean fB, boolean fC, boolean fD, 1286 boolean fE, boolean fF, boolean fG, boolean fH) { 1287 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP6, 1288 fA, fB, fC, fD, fE, fF, fG, fH); 1289 } 1290 1291 public static XNetMessage getFunctionGroup7OpsMsg(int address, 1292 boolean fA, boolean fB, boolean fC, boolean fD, 1293 boolean fE, boolean fF, boolean fG, boolean fH) { 1294 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP7, 1295 fA, fB, fC, fD, fE, fF, fG, fH); 1296 } 1297 1298 public static XNetMessage getFunctionGroup8OpsMsg(int address, 1299 boolean fA, boolean fB, boolean fC, boolean fD, 1300 boolean fE, boolean fF, boolean fG, boolean fH) { 1301 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP8, 1302 fA, fB, fC, fD, fE, fF, fG, fH); 1303 } 1304 1305 public static XNetMessage getFunctionGroup9OpsMsg(int address, 1306 boolean fA, boolean fB, boolean fC, boolean fD, 1307 boolean fE, boolean fF, boolean fG, boolean fH) { 1308 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP9, 1309 fA, fB, fC, fD, fE, fF, fG, fH); 1310 } 1311 1312 public static XNetMessage getFunctionGroup10OpsMsg(int address, 1313 boolean fA, boolean fB, boolean fC, boolean fD, 1314 boolean fE, boolean fF, boolean fG, boolean fH) { 1315 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP10, 1316 fA, fB, fC, fD, fE, fF, fG, fH); 1317 } 1318 1319 public static XNetMessage getFunctionGroup6SetMomMsg(int address, 1320 boolean fA, boolean fB, boolean fC, boolean fD, 1321 boolean fE, boolean fF, boolean fG, boolean fH) { 1322 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP6_MOMENTARY, 1323 fA, fB, fC, fD, fE, fF, fG, fH); 1324 } 1325 1326 public static XNetMessage getFunctionGroup7SetMomMsg(int address, 1327 boolean fA, boolean fB, boolean fC, boolean fD, 1328 boolean fE, boolean fF, boolean fG, boolean fH) { 1329 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP7_MOMENTARY, 1330 fA, fB, fC, fD, fE, fF, fG, fH); 1331 } 1332 1333 public static XNetMessage getFunctionGroup8SetMomMsg(int address, 1334 boolean fA, boolean fB, boolean fC, boolean fD, 1335 boolean fE, boolean fF, boolean fG, boolean fH) { 1336 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP8_MOMENTARY, 1337 fA, fB, fC, fD, fE, fF, fG, fH); 1338 } 1339 1340 public static XNetMessage getFunctionGroup9SetMomMsg(int address, 1341 boolean fA, boolean fB, boolean fC, boolean fD, 1342 boolean fE, boolean fF, boolean fG, boolean fH) { 1343 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP9_MOMENTARY, 1344 fA, fB, fC, fD, fE, fF, fG, fH); 1345 } 1346 1347 public static XNetMessage getFunctionGroup10SetMomMsg(int address, 1348 boolean fA, boolean fB, boolean fC, boolean fD, 1349 boolean fE, boolean fF, boolean fG, boolean fH) { 1350 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP10_MOMENTARY, 1351 fA, fB, fC, fD, fE, fF, fG, fH); 1352 } 1353 1354 /** 1355 * Build a Resume operations Message. 1356 * @return resume message. 1357 */ 1358 public static XNetMessage getResumeOperationsMsg() { 1359 XNetMessage msg = new XNetMessage(3); 1360 msg.setElement(0, XNetConstants.CS_REQUEST); 1361 msg.setElement(1, XNetConstants.RESUME_OPS); 1362 msg.setParity(); 1363 return (msg); 1364 } 1365 1366 /** 1367 * Build an EmergencyOff Message. 1368 * @return emergency off message. 1369 */ 1370 public static XNetMessage getEmergencyOffMsg() { 1371 XNetMessage msg = new XNetMessage(3); 1372 msg.setElement(0, XNetConstants.CS_REQUEST); 1373 msg.setElement(1, XNetConstants.EMERGENCY_OFF); 1374 msg.setParity(); 1375 return (msg); 1376 } 1377 1378 /** 1379 * Build an EmergencyStop Message. 1380 * @return emergency stop message. 1381 */ 1382 public static XNetMessage getEmergencyStopMsg() { 1383 XNetMessage msg = new XNetMessage(2); 1384 msg.setElement(0, XNetConstants.ALL_ESTOP); 1385 msg.setParity(); 1386 return (msg); 1387 } 1388 1389 /** 1390 * Generate the message to request the Command Station Hardware/Software 1391 * Version. 1392 * @return message to request CS hardware and software version. 1393 */ 1394 public static XNetMessage getCSVersionRequestMessage() { 1395 XNetMessage msg = new XNetMessage(3); 1396 msg.setElement(0, XNetConstants.CS_REQUEST); 1397 msg.setElement(1, XNetConstants.CS_VERSION); 1398 msg.setParity(); // Set the parity bit 1399 return msg; 1400 } 1401 1402 /** 1403 * Generate the message to request the Command Station Status. 1404 * @return message to request CS status. 1405 */ 1406 public static XNetMessage getCSStatusRequestMessage() { 1407 XNetMessage msg = new XNetMessage(3); 1408 msg.setElement(0, XNetConstants.CS_REQUEST); 1409 msg.setElement(1, XNetConstants.CS_STATUS); 1410 msg.setParity(); // Set the parity bit 1411 return msg; 1412 } 1413 1414 /** 1415 * Generate the message to set the Command Station to Auto or Manual restart 1416 * mode. 1417 * @param autoMode true if auto, false for manual. 1418 * @return message to set CS restart mode. 1419 */ 1420 public static XNetMessage getCSAutoStartMessage(boolean autoMode) { 1421 XNetMessage msg = new XNetMessage(4); 1422 msg.setElement(0, XNetConstants.CS_SET_POWERMODE); 1423 msg.setElement(1, XNetConstants.CS_SET_POWERMODE); 1424 if (autoMode) { 1425 msg.setElement(2, XNetConstants.CS_POWERMODE_AUTO); 1426 } else { 1427 msg.setElement(2, XNetConstants.CS_POWERMODE_MANUAL); 1428 } 1429 msg.setParity(); // Set the parity bit 1430 return msg; 1431 } 1432 1433 /** 1434 * Generate the message to request the Computer Interface Hardware/Software 1435 * Version. 1436 * @return message to request interface hardware and software version. 1437 */ 1438 public static XNetMessage getLIVersionRequestMessage() { 1439 XNetMessage msg = new XNetMessage(2); 1440 msg.setElement(0, XNetConstants.LI_VERSION_REQUEST); 1441 msg.setParity(); // Set the parity bit 1442 return msg; 1443 } 1444 1445 /** 1446 * Generate the message to set or request the Computer Interface Address. 1447 * 1448 * @param address Interface address (0-31). Send invalid address to request 1449 * the address (32-255). 1450 * @return message to set or request interface address. 1451 */ 1452 public static XNetMessage getLIAddressRequestMsg(int address) { 1453 XNetMessage msg = new XNetMessage(4); 1454 msg.setElement(0, XNetConstants.LI101_REQUEST); 1455 msg.setElement(1, XNetConstants.LI101_REQUEST_ADDRESS); 1456 msg.setElement(2, address); 1457 msg.setParity(); // Set the parity bit 1458 return msg; 1459 } 1460 1461 /** 1462 * Generate the message to set or request the Computer Interface speed. 1463 * 1464 * @param speed 1 is 19,200bps, 2 is 38,400bps, 3 is 57,600bps, 4 is 1465 * 115,200bps. Send invalid speed to request the current 1466 * setting. 1467 * @return message for set / request interface speed. 1468 */ 1469 public static XNetMessage getLISpeedRequestMsg(int speed) { 1470 XNetMessage msg = new XNetMessage(4); 1471 msg.setElement(0, XNetConstants.LI101_REQUEST); 1472 msg.setElement(1, XNetConstants.LI101_REQUEST_BAUD); 1473 msg.setElement(2, speed); 1474 msg.setParity(); // Set the parity bit 1475 return msg; 1476 } 1477 1478 private static final List<XPressNetMessageFormatter> formatterList = new ArrayList<>(); 1479 1480 /** 1481 * Generate text translations of messages for use in the XpressNet monitor. 1482 * 1483 * @return representation of the XNetMessage as a string. 1484 */ 1485 @Override 1486 public String toMonitorString() { 1487 if (formatterList.isEmpty()) { 1488 try { 1489 Reflections reflections = new Reflections("jmri.jmrix"); 1490 Set<Class<? extends XPressNetMessageFormatter>> f = reflections.getSubTypesOf(XPressNetMessageFormatter.class); 1491 for (Class<?> c : f) { 1492 log.debug("Found formatter: {}", f.getClass().getName()); 1493 Constructor<?> ctor = c.getConstructor(); 1494 formatterList.add((XPressNetMessageFormatter) ctor.newInstance()); 1495 } 1496 } catch (NoSuchMethodException | SecurityException | InstantiationException | 1497 IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 1498 log.error("Error instantiating formatter", e); 1499 } 1500 } 1501 1502 return formatterList.stream().filter(f -> f.handlesMessage(this)).findFirst().map(f -> f.formatMessage(this)).orElse(this.toString()); 1503 } 1504 1505 // initialize logging 1506 private static final Logger log = LoggerFactory.getLogger(XNetMessage.class); 1507 1508}