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_READ_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 public static XNetMessage getOpsModeResultsMsg() { 528 XNetMessage m = new XNetMessage(3); 529 m.setTimeout(XNetProgrammingTimeout); 530 m.setElement(0, XNetConstants.CS_REQUEST); 531 m.setElement(1, XNetConstants.OPS_MODE_CSRESULT); 532 m.setParity(); // Set the parity bit 533 return m; 534 } 535 /* 536 * Next, we have routines to generate XpressNet Messages for building 537 * and tearing down a consist or a double header. 538 */ 539 540 /** 541 * Build a Double Header. 542 * 543 * @param address1 the first address in the consist 544 * @param address2 the second address in the consist. 545 * @return message to build double header. 546 */ 547 public static XNetMessage getBuildDoubleHeaderMsg(int address1, int address2) { 548 XNetMessage msg = new XNetMessage(7); 549 msg.setElement(0, XNetConstants.LOCO_DOUBLEHEAD); 550 msg.setElement(1, XNetConstants.LOCO_DOUBLEHEAD_BYTE2); 551 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address1)); 552 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address1)); 553 msg.setElement(4, LenzCommandStation.getDCCAddressHigh(address2)); 554 msg.setElement(5, LenzCommandStation.getDCCAddressLow(address2)); 555 msg.setParity(); 556 return (msg); 557 } 558 559 /** 560 * Dissolve a Double Header. 561 * 562 * @param address one of the two addresses in the Double Header 563 * @return message to dissolve a double header. 564 */ 565 public static XNetMessage getDisolveDoubleHeaderMsg(int address) { 566 // All we have to do is call getBuildDoubleHeaderMsg with the 567 // second address as a zero 568 return (getBuildDoubleHeaderMsg(address, 0)); 569 } 570 571 /** 572 * Add a Single address to a specified Advanced consist. 573 * 574 * @param consist the consist address (1-99) 575 * @param address the locomotive address to add. 576 * @param isNormalDir tells us if the locomotive is going forward when 577 * the consist is going forward. 578 * @return message to add address to consist. 579 */ 580 public static XNetMessage getAddLocoToConsistMsg(int consist, int address, 581 boolean isNormalDir) { 582 XNetMessage msg = new XNetMessage(6); 583 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 584 if (isNormalDir) { 585 msg.setElement(1, XNetConstants.LOCO_ADD_MULTI_UNIT_REQ); 586 } else { 587 msg.setElement(1, XNetConstants.LOCO_ADD_MULTI_UNIT_REQ | 0x01); 588 } 589 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 590 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 591 msg.setElement(4, consist); 592 msg.setParity(); 593 return (msg); 594 } 595 596 /** 597 * Remove a Single address to a specified Advanced consist. 598 * 599 * @param consist the consist address (1-99) 600 * @param address the locomotive address to remove 601 * @return message to remove single address from consist. 602 */ 603 public static XNetMessage getRemoveLocoFromConsistMsg(int consist, int address) { 604 XNetMessage msg = new XNetMessage(6); 605 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 606 msg.setElement(1, XNetConstants.LOCO_REM_MULTI_UNIT_REQ); 607 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 608 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 609 msg.setElement(4, consist); 610 msg.setParity(); 611 return (msg); 612 } 613 614 615 /* 616 * Next, we have routines to generate XpressNet Messages for search 617 * and manipulation of the Command Station Database 618 */ 619 620 /** 621 * Given a locomotive address, search the database for the next 622 * member. 623 * (if the Address is zero start at the beginning of the database). 624 * 625 * @param address is the locomotive address 626 * @param searchForward indicates to search the database Forward if 627 * true, or backwards if False 628 * @return message to request next address. 629 */ 630 public static XNetMessage getNextAddressOnStackMsg(int address, boolean searchForward) { 631 XNetMessage msg = new XNetMessage(5); 632 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 633 if (searchForward) { 634 msg.setElement(1, XNetConstants.LOCO_STACK_SEARCH_FWD); 635 } else { 636 msg.setElement(1, XNetConstants.LOCO_STACK_SEARCH_BKWD); 637 } 638 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 639 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 640 msg.setParity(); 641 return (msg); 642 } 643 644 /** 645 * Given a consist address, search the database for the next Consist 646 * address. 647 * 648 * @param address is the consist address (in the range 1-99). 649 * If the Address is zero start at the beginning of the database. 650 * @param searchForward indicates to search the database Forward if 651 * true, or backwards if false 652 * @return message to get next consist address. 653 */ 654 public static XNetMessage getDBSearchMsgConsistAddress(int address, boolean searchForward) { 655 XNetMessage msg = new XNetMessage(4); 656 msg.setElement(0, XNetConstants.CS_MULTI_UNIT_REQ); 657 if (searchForward) { 658 msg.setElement(1, XNetConstants.CS_MULTI_UNIT_REQ_FWD); 659 } else { 660 msg.setElement(1, XNetConstants.CS_MULTI_UNIT_REQ_BKWD); 661 } 662 msg.setElement(2, address); 663 msg.setParity(); 664 return (msg); 665 } 666 667 /** 668 * Given a consist and a locomotive address, search the database for 669 * the next Locomotive in the consist. 670 * 671 * @param consist the consist address (1-99). 672 * If the Consist Address is zero start at the begining of the database 673 * @param address the locomotive address. 674 * If the Address is zero start at the begining of the consist 675 * @param searchForward indicates to search the database Forward if 676 * true, or backwards if False 677 * @return message to request next loco in consist. 678 */ 679 public static XNetMessage getDBSearchMsgNextMULoco(int consist, int address, boolean searchForward) { 680 XNetMessage msg = new XNetMessage(6); 681 msg.setElement(0, XNetConstants.LOCO_IN_MULTI_UNIT_SEARCH_REQ); 682 if (searchForward) { 683 msg.setElement(1, XNetConstants.LOCO_IN_MULTI_UNIT_REQ_FORWARD); 684 } else { 685 msg.setElement(1, XNetConstants.LOCO_IN_MULTI_UNIT_REQ_BACKWARD); 686 } 687 msg.setElement(2, consist); 688 msg.setElement(3, LenzCommandStation.getDCCAddressHigh(address)); 689 msg.setElement(4, LenzCommandStation.getDCCAddressLow(address)); 690 msg.setParity(); 691 return (msg); 692 } 693 694 /** 695 * Given a locomotive address, delete it from the database . 696 * 697 * @param address the locomotive address 698 * @return message to delete loco address from stack. 699 */ 700 public static XNetMessage getDeleteAddressOnStackMsg(int address) { 701 XNetMessage msg = new XNetMessage(5); 702 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 703 msg.setElement(1, XNetConstants.LOCO_STACK_DELETE); 704 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 705 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 706 msg.setParity(); 707 return (msg); 708 } 709 710 /** 711 * Given a locomotive address, request its status . 712 * 713 * @param address the locomotive address 714 * @return message to request loco status. 715 */ 716 public static XNetMessage getLocomotiveInfoRequestMsg(int address) { 717 XNetMessage msg = new XNetMessage(5); 718 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 719 msg.setElement(1, XNetConstants.LOCO_INFO_REQ_V3); 720 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 721 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 722 msg.setParity(); 723 return (msg); 724 } 725 726 /** 727 * Given a locomotive address, request the function state (momentary status). 728 * 729 * @param address the locomotive address 730 * @return momentary function state request request. 731 */ 732 public static XNetMessage getLocomotiveFunctionStatusMsg(int address) { 733 XNetMessage msg = new XNetMessage(5); 734 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 735 msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC); 736 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 737 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 738 msg.setParity(); 739 return (msg); 740 } 741 742 /** 743 * Given a locomotive address, request the function on/off state 744 * for functions 13-28 745 * 746 * @param address the locomotive address 747 * @return function state request request f13-f28. 748 */ 749 public static XNetMessage getLocomotiveFunctionHighOnStatusMsg(int address) { 750 XNetMessage msg = new XNetMessage(5); 751 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 752 msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC_HI_ON); 753 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 754 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 755 msg.setParity(); 756 return (msg); 757 } 758 759 /** 760 * Given a locomotive address, request the function state (momentary status) 761 * for high functions (functions 13-28). 762 * 763 * @param address the locomotive address 764 * @return momentary function state request request f13-f28. 765 */ 766 public static XNetMessage getLocomotiveFunctionHighMomStatusMsg(int address) { 767 XNetMessage msg = new XNetMessage(5); 768 msg.setElement(0, XNetConstants.LOCO_STATUS_REQ); 769 msg.setElement(1, XNetConstants.LOCO_INFO_REQ_FUNC_HI_MOM); 770 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 771 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 772 msg.setParity(); 773 return (msg); 774 } 775 776 /* 777 * Generate an emergency stop for the specified address. 778 * 779 * @param address the locomotive address 780 */ 781 public static XNetMessage getAddressedEmergencyStop(int address) { 782 XNetMessage msg = new XNetMessage(4); 783 msg.setElement(0, XNetConstants.EMERGENCY_STOP); 784 msg.setElement(1, LenzCommandStation.getDCCAddressHigh(address)); 785 // set to the upper 786 // byte of the DCC address 787 msg.setElement(2, LenzCommandStation.getDCCAddressLow(address)); 788 // set to the lower byte 789 //of the DCC address 790 msg.setParity(); // Set the parity bit 791 return msg; 792 } 793 794 /** 795 * Generate a Speed and Direction Request message. 796 * 797 * @param address the locomotive address 798 * @param speedStepMode the speedstep mode see @jmri.DccThrottle 799 * for possible values. 800 * @param speed a normalized speed value (a floating point number between 0 801 * and 1). A negative value indicates emergency stop. 802 * @param isForward true for forward, false for reverse. 803 * @return set speed and direction message. 804 */ 805 public static XNetMessage getSpeedAndDirectionMsg(int address, 806 SpeedStepMode speedStepMode, 807 float speed, 808 boolean isForward) { 809 XNetMessage msg = new XNetMessage(6); 810 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 811 int element4value = 0; /* this is for holding the speed and 812 direction setting */ 813 814 if (speedStepMode == SpeedStepMode.NMRA_DCC_128) { 815 // We're in 128 speed step mode 816 msg.setElement(1, XNetConstants.LOCO_SPEED_128); 817 // Now, we need to figure out what to send in element 4 818 // Remember, the speed steps are identified as 0-127 (in 819 // 128 step mode), not 1-128. 820 int speedVal = java.lang.Math.round(speed * 126); 821 // speed step 1 is reserved to indicate emergency stop, 822 // so we need to step over speed step 1 823 if (speedVal >= 1) { 824 element4value = speedVal + 1; 825 } 826 } else if (speedStepMode == SpeedStepMode.NMRA_DCC_28) { 827 // We're in 28 speed step mode 828 msg.setElement(1, XNetConstants.LOCO_SPEED_28); 829 // Now, we need to figure out what to send in element 4 830 int speedVal = java.lang.Math.round(speed * 28); 831 // The first speed step used is actually at 4 for 28 832 // speed step mode. 833 if (speedVal >= 1) { 834 speedVal += 3; 835 } 836 // We have to re-arange the bits, since bit 4 is the LSB, 837 // but other bits are in order from 0-3 838 element4value = ((speedVal & 0x1e) >> 1) 839 + ((speedVal & 0x01) << 4); 840 } else if (speedStepMode == SpeedStepMode.NMRA_DCC_27) { 841 // We're in 27 speed step mode 842 msg.setElement(1, XNetConstants.LOCO_SPEED_27); 843 // Now, we need to figure out what to send in element 4 844 int speedVal = java.lang.Math.round(speed * 27); 845 // The first speed step used is actually at 4 for 27 846 // speed step mode. 847 if (speedVal >= 1) { 848 speedVal += 3; 849 } 850 // We have to re-arange the bits, since bit 4 is the LSB, 851 // but other bits are in order from 0-3 852 element4value = ((speedVal & 0x1e) >> 1) 853 + ((speedVal & 0x01) << 4); 854 } else { 855 // We're in 14 speed step mode 856 msg.setElement(1, XNetConstants.LOCO_SPEED_14); 857 // Now, we need to figure out what to send in element 4 858 element4value = (int) (speed * 14); 859 int speedVal = java.lang.Math.round(speed * 14); 860 // The first speed step used is actually at 2 for 14 861 // speed step mode. 862 if (speedVal >= 1) { 863 element4value += 1; 864 } 865 } 866 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 867 // set to the upper byte of the DCC address 868 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 869 // set to the lower byte 870 //of the DCC address 871 if (isForward) { 872 /* the direction bit is always the most significant bit */ 873 element4value += 128; 874 } 875 msg.setElement(4, element4value); 876 msg.setParity(); // Set the parity bit 877 return msg; 878 } 879 880 /** 881 * Generate a Function Group One Operation Request message. 882 * 883 * @param address the locomotive address 884 * @param f0 is true if f0 is on, false if f0 is off 885 * @param f1 is true if f1 is on, false if f1 is off 886 * @param f2 is true if f2 is on, false if f2 is off 887 * @param f3 is true if f3 is on, false if f3 is off 888 * @param f4 is true if f4 is on, false if f4 is off 889 * @return set function group 1 message. 890 */ 891 public static XNetMessage getFunctionGroup1OpsMsg(int address, 892 boolean f0, 893 boolean f1, 894 boolean f2, 895 boolean f3, 896 boolean f4) { 897 XNetMessage msg = new XNetMessage(6); 898 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 899 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP1); 900 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 901 // set to the upper byte of the DCC address 902 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 903 // set to the lower byte of the DCC address 904 // Now, we need to figure out what to send in element 3 905 int element4value = 0; 906 if (f0) { 907 element4value += 16; 908 } 909 if (f1) { 910 element4value += 1; 911 } 912 if (f2) { 913 element4value += 2; 914 } 915 if (f3) { 916 element4value += 4; 917 } 918 if (f4) { 919 element4value += 8; 920 } 921 msg.setElement(4, element4value); 922 msg.setParity(); // Set the parity bit 923 return msg; 924 } 925 926 /** 927 * Generate a Function Group One Set Momentary Functions message. 928 * 929 * @param address the locomotive address 930 * @param f0 is true if f0 is momentary 931 * @param f1 is true if f1 is momentary 932 * @param f2 is true if f2 is momentary 933 * @param f3 is true if f3 is momentary 934 * @param f4 is true if f4 is momentary 935 * @return set momentary function group 1 message. 936 */ 937 public static XNetMessage getFunctionGroup1SetMomMsg(int address, 938 boolean f0, 939 boolean f1, 940 boolean f2, 941 boolean f3, 942 boolean f4) { 943 XNetMessage msg = new XNetMessage(6); 944 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 945 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP1_MOMENTARY); 946 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 947 // set to the upper byte of the DCC address 948 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 949 // set to the lower byte of the DCC address 950 // Now, we need to figure out what to send in element 3 951 int element4value = 0; 952 if (f0) { 953 element4value += 16; 954 } 955 if (f1) { 956 element4value += 1; 957 } 958 if (f2) { 959 element4value += 2; 960 } 961 if (f3) { 962 element4value += 4; 963 } 964 if (f4) { 965 element4value += 8; 966 } 967 msg.setElement(4, element4value); 968 msg.setParity(); // Set the parity bit 969 return msg; 970 } 971 972 /** 973 * Generate a Function Group Two Operation Request message. 974 * 975 * @param address the locomotive address 976 * @param f5 is true if f5 is on, false if f5 is off 977 * @param f6 is true if f6 is on, false if f6 is off 978 * @param f7 is true if f7 is on, false if f7 is off 979 * @param f8 is true if f8 is on, false if f8 is off 980 * @return set function group 2 message. 981 */ 982 public static XNetMessage getFunctionGroup2OpsMsg(int address, 983 boolean f5, 984 boolean f6, 985 boolean f7, 986 boolean f8) { 987 XNetMessage msg = new XNetMessage(6); 988 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 989 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP2); 990 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 991 // set to the upper byte of the DCC address 992 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 993 // set to the lower byte of the DCC address 994 // Now, we need to figure out what to send in element 3 995 int element4value = 0; 996 if (f5) { 997 element4value += 1; 998 } 999 if (f6) { 1000 element4value += 2; 1001 } 1002 if (f7) { 1003 element4value += 4; 1004 } 1005 if (f8) { 1006 element4value += 8; 1007 } 1008 msg.setElement(4, element4value); 1009 msg.setParity(); // Set the parity bit 1010 return msg; 1011 } 1012 1013 /** 1014 * Generate a Function Group Two Set Momentary Functions message. 1015 * 1016 * @param address the locomotive address 1017 * @param f5 is true if f5 is momentary 1018 * @param f6 is true if f6 is momentary 1019 * @param f7 is true if f7 is momentary 1020 * @param f8 is true if f8 is momentary 1021 * @return set momentary function group 2 message. 1022 */ 1023 public static XNetMessage getFunctionGroup2SetMomMsg(int address, 1024 boolean f5, 1025 boolean f6, 1026 boolean f7, 1027 boolean f8) { 1028 XNetMessage msg = new XNetMessage(6); 1029 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 1030 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP2_MOMENTARY); 1031 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 1032 // set to the upper byte of the DCC address 1033 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 1034 // set to the lower byte of the DCC address 1035 // Now, we need to figure out what to send in element 3 1036 int element4value = 0; 1037 if (f5) { 1038 element4value += 1; 1039 } 1040 if (f6) { 1041 element4value += 2; 1042 } 1043 if (f7) { 1044 element4value += 4; 1045 } 1046 if (f8) { 1047 element4value += 8; 1048 } 1049 msg.setElement(4, element4value); 1050 msg.setParity(); // Set the parity bit 1051 return msg; 1052 } 1053 1054 /** 1055 * Generate a Function Group Three Operation Request message. 1056 * 1057 * @param address the locomotive address 1058 * @param f9 is true if f9 is on, false if f9 is off 1059 * @param f10 is true if f10 is on, false if f10 is off 1060 * @param f11 is true if f11 is on, false if f11 is off 1061 * @param f12 is true if f12 is on, false if f12 is off 1062 * @return set function group 3 message. 1063 */ 1064 public static XNetMessage getFunctionGroup3OpsMsg(int address, 1065 boolean f9, 1066 boolean f10, 1067 boolean f11, 1068 boolean f12) { 1069 XNetMessage msg = new XNetMessage(6); 1070 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 1071 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP3); 1072 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 1073 // set to the upper byte of the DCC address 1074 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 1075 // set to the lower byte of the DCC address 1076 // Now, we need to figure out what to send in element 3 1077 int element4value = 0; 1078 if (f9) { 1079 element4value += 1; 1080 } 1081 if (f10) { 1082 element4value += 2; 1083 } 1084 if (f11) { 1085 element4value += 4; 1086 } 1087 if (f12) { 1088 element4value += 8; 1089 } 1090 msg.setElement(4, element4value); 1091 msg.setParity(); // Set the parity bit 1092 return msg; 1093 } 1094 1095 /** 1096 * Generate a Function Group Three Set Momentary Functions message. 1097 * 1098 * @param address the locomotive address 1099 * @param f9 is true if f9 is momentary 1100 * @param f10 is true if f10 is momentary 1101 * @param f11 is true if f11 is momentary 1102 * @param f12 is true if f12 is momentary 1103 * @return set momentary function group 3 message. 1104 */ 1105 public static XNetMessage getFunctionGroup3SetMomMsg(int address, 1106 boolean f9, 1107 boolean f10, 1108 boolean f11, 1109 boolean f12) { 1110 XNetMessage msg = new XNetMessage(6); 1111 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 1112 msg.setElement(1, XNetConstants.LOCO_SET_FUNC_GROUP3_MOMENTARY); 1113 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 1114 // set to the upper byte of the DCC address 1115 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 1116 // set to the lower byte of the DCC address 1117 // Now, we need to figure out what to send in element 3 1118 int element4value = 0; 1119 if (f9) { 1120 element4value += 1; 1121 } 1122 if (f10) { 1123 element4value += 2; 1124 } 1125 if (f11) { 1126 element4value += 4; 1127 } 1128 if (f12) { 1129 element4value += 8; 1130 } 1131 msg.setElement(4, element4value); 1132 msg.setParity(); // Set the parity bit 1133 return msg; 1134 } 1135 1136 /** 1137 * Generate a Function Group Four Operation Request message. 1138 * 1139 * @param address the locomotive address 1140 * @param f13 is true if f13 is on, false if f13 is off 1141 * @param f14 is true if f14 is on, false if f14 is off 1142 * @param f15 is true if f15 is on, false if f15 is off 1143 * @param f16 is true if f18 is on, false if f16 is off 1144 * @param f17 is true if f17 is on, false if f17 is off 1145 * @param f18 is true if f18 is on, false if f18 is off 1146 * @param f19 is true if f19 is on, false if f19 is off 1147 * @param f20 is true if f20 is on, false if f20 is off 1148 * @return set function group 4 message. 1149 */ 1150 public static XNetMessage getFunctionGroup4OpsMsg(int address, 1151 boolean f13, 1152 boolean f14, 1153 boolean f15, 1154 boolean f16, 1155 boolean f17, 1156 boolean f18, 1157 boolean f19, 1158 boolean f20) { 1159 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP4, 1160 f13, f14, f15, f16, f17, f18, f19, f20); 1161 } 1162 1163 /** 1164 * Generate a Function Group Four Set Momentary Function message. 1165 * 1166 * @param address the locomotive address 1167 * @param f13 is true if f13 is Momentary 1168 * @param f14 is true if f14 is Momentary 1169 * @param f15 is true if f15 is Momentary 1170 * @param f16 is true if f18 is Momentary 1171 * @param f17 is true if f17 is Momentary 1172 * @param f18 is true if f18 is Momentary 1173 * @param f19 is true if f19 is Momentary 1174 * @param f20 is true if f20 is Momentary 1175 * @return set momentary function group 4 message. 1176 */ 1177 public static XNetMessage getFunctionGroup4SetMomMsg(int address, 1178 boolean f13, 1179 boolean f14, 1180 boolean f15, 1181 boolean f16, 1182 boolean f17, 1183 boolean f18, 1184 boolean f19, 1185 boolean f20) { 1186 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP4_MOMENTARY, 1187 f13, f14, f15, f16, f17, f18, f19, f20); 1188 } 1189 1190 /** 1191 * Generate a Function Group Five Operation Request message. 1192 * 1193 * @param address the locomotive address 1194 * @param f21 is true if f21 is on, false if f21 is off 1195 * @param f22 is true if f22 is on, false if f22 is off 1196 * @param f23 is true if f23 is on, false if f23 is off 1197 * @param f24 is true if f24 is on, false if f24 is off 1198 * @param f25 is true if f25 is on, false if f25 is off 1199 * @param f26 is true if f26 is on, false if f26 is off 1200 * @param f27 is true if f27 is on, false if f27 is off 1201 * @param f28 is true if f28 is on, false if f28 is off 1202 * @return set function group 5 message. 1203 */ 1204 public static XNetMessage getFunctionGroup5OpsMsg(int address, 1205 boolean f21, 1206 boolean f22, 1207 boolean f23, 1208 boolean f24, 1209 boolean f25, 1210 boolean f26, 1211 boolean f27, 1212 boolean f28) { 1213 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP5, 1214 f21, f22, f23, f24, f25, f26, f27, f28); 1215 } 1216 1217 /** 1218 * Generate a Function Group Five Set Momentary Function message. 1219 * 1220 * @param address the locomotive address 1221 * @param f21 is true if f21 is momentary 1222 * @param f22 is true if f22 is momentary 1223 * @param f23 is true if f23 is momentary 1224 * @param f24 is true if f24 is momentary 1225 * @param f25 is true if f25 is momentary 1226 * @param f26 is true if f26 is momentary 1227 * @param f27 is true if f27 is momentary 1228 * @param f28 is true if f28 is momentary 1229 * @return set momentary function group 5 message. 1230 */ 1231 public static XNetMessage getFunctionGroup5SetMomMsg(int address, 1232 boolean f21, 1233 boolean f22, 1234 boolean f23, 1235 boolean f24, 1236 boolean f25, 1237 boolean f26, 1238 boolean f27, 1239 boolean f28) { 1240 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP5_MOMENTARY, 1241 f21, f22, f23, f24, f25, f26, f27, f28); 1242 } 1243 1244 // Generate a Function Group Operation Request message for some specific case. 1245 private static XNetMessage getFunctionGroupNOpsMsg(int address, int byte1, 1246 boolean fA, 1247 boolean fB, 1248 boolean fC, 1249 boolean fD, 1250 boolean fE, 1251 boolean fF, 1252 boolean fG, 1253 boolean fH) { 1254 XNetMessage msg = new XNetMessage(6); 1255 msg.setElement(0, XNetConstants.LOCO_OPER_REQ); 1256 msg.setElement(1, byte1); 1257 msg.setElement(2, LenzCommandStation.getDCCAddressHigh(address)); 1258 // set to the upper byte of the DCC address 1259 msg.setElement(3, LenzCommandStation.getDCCAddressLow(address)); 1260 // set to the lower byte of the DCC address 1261 // Now, we need to figure out what to send in element 3 1262 int element4value = 0; 1263 if (fA) { 1264 element4value += 1; 1265 } 1266 if (fB) { 1267 element4value += 2; 1268 } 1269 if (fC) { 1270 element4value += 4; 1271 } 1272 if (fD) { 1273 element4value += 8; 1274 } 1275 if (fE) { 1276 element4value += 16; 1277 } 1278 if (fF) { 1279 element4value += 32; 1280 } 1281 if (fG) { 1282 element4value += 64; 1283 } 1284 if (fH) { 1285 element4value += 128; 1286 } 1287 msg.setElement(4, element4value); 1288 msg.setParity(); // Set the parity bit 1289 return msg; 1290 } 1291 1292 public static XNetMessage getFunctionGroup6OpsMsg(int address, 1293 boolean fA, boolean fB, boolean fC, boolean fD, 1294 boolean fE, boolean fF, boolean fG, boolean fH) { 1295 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP6, 1296 fA, fB, fC, fD, fE, fF, fG, fH); 1297 } 1298 1299 public static XNetMessage getFunctionGroup7OpsMsg(int address, 1300 boolean fA, boolean fB, boolean fC, boolean fD, 1301 boolean fE, boolean fF, boolean fG, boolean fH) { 1302 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP7, 1303 fA, fB, fC, fD, fE, fF, fG, fH); 1304 } 1305 1306 public static XNetMessage getFunctionGroup8OpsMsg(int address, 1307 boolean fA, boolean fB, boolean fC, boolean fD, 1308 boolean fE, boolean fF, boolean fG, boolean fH) { 1309 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP8, 1310 fA, fB, fC, fD, fE, fF, fG, fH); 1311 } 1312 1313 public static XNetMessage getFunctionGroup9OpsMsg(int address, 1314 boolean fA, boolean fB, boolean fC, boolean fD, 1315 boolean fE, boolean fF, boolean fG, boolean fH) { 1316 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP9, 1317 fA, fB, fC, fD, fE, fF, fG, fH); 1318 } 1319 1320 public static XNetMessage getFunctionGroup10OpsMsg(int address, 1321 boolean fA, boolean fB, boolean fC, boolean fD, 1322 boolean fE, boolean fF, boolean fG, boolean fH) { 1323 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP10, 1324 fA, fB, fC, fD, fE, fF, fG, fH); 1325 } 1326 1327 public static XNetMessage getFunctionGroup6SetMomMsg(int address, 1328 boolean fA, boolean fB, boolean fC, boolean fD, 1329 boolean fE, boolean fF, boolean fG, boolean fH) { 1330 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP6_MOMENTARY, 1331 fA, fB, fC, fD, fE, fF, fG, fH); 1332 } 1333 1334 public static XNetMessage getFunctionGroup7SetMomMsg(int address, 1335 boolean fA, boolean fB, boolean fC, boolean fD, 1336 boolean fE, boolean fF, boolean fG, boolean fH) { 1337 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP7_MOMENTARY, 1338 fA, fB, fC, fD, fE, fF, fG, fH); 1339 } 1340 1341 public static XNetMessage getFunctionGroup8SetMomMsg(int address, 1342 boolean fA, boolean fB, boolean fC, boolean fD, 1343 boolean fE, boolean fF, boolean fG, boolean fH) { 1344 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP8_MOMENTARY, 1345 fA, fB, fC, fD, fE, fF, fG, fH); 1346 } 1347 1348 public static XNetMessage getFunctionGroup9SetMomMsg(int address, 1349 boolean fA, boolean fB, boolean fC, boolean fD, 1350 boolean fE, boolean fF, boolean fG, boolean fH) { 1351 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP9_MOMENTARY, 1352 fA, fB, fC, fD, fE, fF, fG, fH); 1353 } 1354 1355 public static XNetMessage getFunctionGroup10SetMomMsg(int address, 1356 boolean fA, boolean fB, boolean fC, boolean fD, 1357 boolean fE, boolean fF, boolean fG, boolean fH) { 1358 return getFunctionGroupNOpsMsg(address, XNetConstants.LOCO_SET_FUNC_GROUP10_MOMENTARY, 1359 fA, fB, fC, fD, fE, fF, fG, fH); 1360 } 1361 1362 /** 1363 * Build a Resume operations Message. 1364 * @return resume message. 1365 */ 1366 public static XNetMessage getResumeOperationsMsg() { 1367 XNetMessage msg = new XNetMessage(3); 1368 msg.setElement(0, XNetConstants.CS_REQUEST); 1369 msg.setElement(1, XNetConstants.RESUME_OPS); 1370 msg.setParity(); 1371 return (msg); 1372 } 1373 1374 /** 1375 * Build an EmergencyOff Message. 1376 * @return emergency off message. 1377 */ 1378 public static XNetMessage getEmergencyOffMsg() { 1379 XNetMessage msg = new XNetMessage(3); 1380 msg.setElement(0, XNetConstants.CS_REQUEST); 1381 msg.setElement(1, XNetConstants.EMERGENCY_OFF); 1382 msg.setParity(); 1383 return (msg); 1384 } 1385 1386 /** 1387 * Build an EmergencyStop Message. 1388 * @return emergency stop message. 1389 */ 1390 public static XNetMessage getEmergencyStopMsg() { 1391 XNetMessage msg = new XNetMessage(2); 1392 msg.setElement(0, XNetConstants.ALL_ESTOP); 1393 msg.setParity(); 1394 return (msg); 1395 } 1396 1397 /** 1398 * Generate the message to request the Command Station Hardware/Software 1399 * Version. 1400 * @return message to request CS hardware and software version. 1401 */ 1402 public static XNetMessage getCSVersionRequestMessage() { 1403 XNetMessage msg = new XNetMessage(3); 1404 msg.setElement(0, XNetConstants.CS_REQUEST); 1405 msg.setElement(1, XNetConstants.CS_VERSION); 1406 msg.setParity(); // Set the parity bit 1407 return msg; 1408 } 1409 1410 /** 1411 * Generate the message to request the Command Station Status. 1412 * @return message to request CS status. 1413 */ 1414 public static XNetMessage getCSStatusRequestMessage() { 1415 XNetMessage msg = new XNetMessage(3); 1416 msg.setElement(0, XNetConstants.CS_REQUEST); 1417 msg.setElement(1, XNetConstants.CS_STATUS); 1418 msg.setParity(); // Set the parity bit 1419 return msg; 1420 } 1421 1422 /** 1423 * Generate the message to set the Command Station to Auto or Manual restart 1424 * mode. 1425 * @param autoMode true if auto, false for manual. 1426 * @return message to set CS restart mode. 1427 */ 1428 public static XNetMessage getCSAutoStartMessage(boolean autoMode) { 1429 XNetMessage msg = new XNetMessage(4); 1430 msg.setElement(0, XNetConstants.CS_SET_POWERMODE); 1431 msg.setElement(1, XNetConstants.CS_SET_POWERMODE); 1432 if (autoMode) { 1433 msg.setElement(2, XNetConstants.CS_POWERMODE_AUTO); 1434 } else { 1435 msg.setElement(2, XNetConstants.CS_POWERMODE_MANUAL); 1436 } 1437 msg.setParity(); // Set the parity bit 1438 return msg; 1439 } 1440 1441 /** 1442 * Generate the message to request the Computer Interface Hardware/Software 1443 * Version. 1444 * @return message to request interface hardware and software version. 1445 */ 1446 public static XNetMessage getLIVersionRequestMessage() { 1447 XNetMessage msg = new XNetMessage(2); 1448 msg.setElement(0, XNetConstants.LI_VERSION_REQUEST); 1449 msg.setParity(); // Set the parity bit 1450 return msg; 1451 } 1452 1453 /** 1454 * Generate the message to set or request the Computer Interface Address. 1455 * 1456 * @param address Interface address (0-31). Send invalid address to request 1457 * the address (32-255). 1458 * @return message to set or request interface address. 1459 */ 1460 public static XNetMessage getLIAddressRequestMsg(int address) { 1461 XNetMessage msg = new XNetMessage(4); 1462 msg.setElement(0, XNetConstants.LI101_REQUEST); 1463 msg.setElement(1, XNetConstants.LI101_REQUEST_ADDRESS); 1464 msg.setElement(2, address); 1465 msg.setParity(); // Set the parity bit 1466 return msg; 1467 } 1468 1469 /** 1470 * Generate the message to set or request the Computer Interface speed. 1471 * 1472 * @param speed 1 is 19,200bps, 2 is 38,400bps, 3 is 57,600bps, 4 is 1473 * 115,200bps. Send invalid speed to request the current 1474 * setting. 1475 * @return message for set / request interface speed. 1476 */ 1477 public static XNetMessage getLISpeedRequestMsg(int speed) { 1478 XNetMessage msg = new XNetMessage(4); 1479 msg.setElement(0, XNetConstants.LI101_REQUEST); 1480 msg.setElement(1, XNetConstants.LI101_REQUEST_BAUD); 1481 msg.setElement(2, speed); 1482 msg.setParity(); // Set the parity bit 1483 return msg; 1484 } 1485 1486 private static final List<XPressNetMessageFormatter> formatterList = new ArrayList<>(); 1487 1488 /** 1489 * Generate text translations of messages for use in the XpressNet monitor. 1490 * 1491 * @return representation of the XNetMessage as a string. 1492 */ 1493 @Override 1494 public String toMonitorString() { 1495 if (formatterList.isEmpty()) { 1496 try { 1497 Reflections reflections = new Reflections("jmri.jmrix"); 1498 Set<Class<? extends XPressNetMessageFormatter>> f = reflections.getSubTypesOf(XPressNetMessageFormatter.class); 1499 for (Class<?> c : f) { 1500 log.debug("Found formatter: {}", f.getClass().getName()); 1501 Constructor<?> ctor = c.getConstructor(); 1502 formatterList.add((XPressNetMessageFormatter) ctor.newInstance()); 1503 } 1504 } catch (NoSuchMethodException | SecurityException | InstantiationException | 1505 IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 1506 log.error("Error instantiating formatter", e); 1507 } 1508 } 1509 1510 return formatterList.stream().filter(f -> f.handlesMessage(this)).findFirst().map(f -> f.formatMessage(this)).orElse(this.toString()); 1511 } 1512 1513 // initialize logging 1514 private static final Logger log = LoggerFactory.getLogger(XNetMessage.class); 1515 1516}