001package jmri.jmrix.lenz; 002 003import org.reflections.Reflections; 004import org.slf4j.Logger; 005import org.slf4j.LoggerFactory; 006 007import java.lang.reflect.Constructor; 008import java.lang.reflect.InvocationTargetException; 009import java.util.ArrayList; 010import java.util.List; 011import java.util.Optional; 012import java.util.Set; 013import java.util.function.Function; 014import javax.annotation.CheckForNull; 015import javax.annotation.Nonnull; 016 017/** 018 * Represents a single response from the XpressNet. 019 * 020 * @author Paul Bender Copyright (C) 2004 021 * 022 */ 023public class XNetReply extends jmri.jmrix.AbstractMRReply { 024 025 026 // unsolicited by message type. 027 028 // Create a new reply. 029 public XNetReply() { 030 super(); 031 setBinary(true); 032 } 033 034 // Create a new reply from an existing reply 035 public XNetReply(XNetReply reply) { 036 super(reply); 037 setBinary(true); 038 } 039 040 /** 041 * Create a reply from an XNetMessage. 042 * @param message existing message. 043 */ 044 public XNetReply(XNetMessage message) { 045 super(); 046 setBinary(true); 047 for (int i = 0; i < message.getNumDataElements(); i++) { 048 setElement(i, message.getElement(i)); 049 } 050 } 051 052 /** 053 * Create a reply from a string of hex characters. 054 * @param message hex string of message. 055 */ 056 public XNetReply(String message) { 057 super(); 058 setBinary(true); 059 // gather bytes in result 060 byte[] b= jmri.util.StringUtil.bytesFromHexString(message); 061 if (b.length == 0) { 062 // no such thing as a zero-length message 063 _nDataChars = 0; 064 _dataChars = null; 065 return; 066 } 067 _nDataChars = b.length; 068 _dataChars = new int[_nDataChars]; 069 for (int i = 0; i < b.length; i++) { 070 setElement(i, ( b[i] & 0xff) ); 071 } 072 } 073 074 /** 075 * Get the opcode as a string in hex format. 076 * @return 0x hex string of OpCode. 077 */ 078 public String getOpCodeHex() { 079 return "0x" + Integer.toHexString(this.getOpCode()); 080 } 081 082 /** 083 * Check whether the message has a valid parity. 084 * @return true if parity valid, else false. 085 */ 086 public boolean checkParity() { 087 int len = getNumDataElements(); 088 int chksum = 0x00; /* the seed */ 089 090 int loop; 091 092 for (loop = 0; loop < len - 1; loop++) { // calculate contents for data part 093 chksum ^= getElement(loop); 094 } 095 return ((chksum & 0xFF) == getElement(len - 1)); 096 } 097 098 public void setParity() { 099 int len = getNumDataElements(); 100 int chksum = 0x00; /* the seed */ 101 102 int loop; 103 104 for (loop = 0; loop < len - 1; loop++) { // calculate contents for data part 105 chksum ^= getElement(loop); 106 } 107 setElement(len - 1, chksum & 0xFF); 108 } 109 110 /** 111 * Get an integer representation of a BCD value. 112 * 113 * @param n byte in message to convert 114 * @return Integer value of BCD byte. 115 */ 116 public Integer getElementBCD(int n) { 117 return Integer.decode(Integer.toHexString(getElement(n))); 118 } 119 120 /** 121 * skipPrefix is not used at this point in time, but is 122 * defined as abstract in AbstractMRReply 123 */ 124 @Override 125 protected int skipPrefix(int index) { 126 return -1; 127 } 128 129 // decode messages of a particular form 130 131 /* 132 * The next group of routines are used by Feedback and/or turnout 133 * control code. These are used in multiple places within the code, 134 * so they appear here. 135 */ 136 137 /** 138 * If this is a feedback response message for a turnout, return the address. 139 * Otherwise return -1. 140 * 141 * @return the integer address or -1 if not a turnout message 142 */ 143 public int getTurnoutMsgAddr() { 144 if (this.isFeedbackMessage()) { 145 return getTurnoutAddrFromData( 146 getElement(1), 147 getElement(2)); 148 } 149 return -1; 150 } 151 152 private int getTurnoutAddrFromData(int a1, int a2) { 153 if (getFeedbackMessageType() > 1) { 154 return -1; 155 } 156 int address = (a1 & 0xff) * 4 + 1; 157 if ((a2 & 0x10) != 0) { 158 address += 2; 159 } 160 return address; 161 } 162 163 /** 164 * If this is a feedback broadcast message and the specified startbyte is 165 * the address byte of an addres byte data byte pair for a turnout, return 166 * the address. Otherwise return -1. 167 * 168 * @param startByte address byte of the address byte/data byte pair. 169 * @return the integer address or -1 if not a turnout message 170 */ 171 public int getTurnoutMsgAddr(int startByte) { 172 if (this.isFeedbackBroadcastMessage()) { 173 int a1 = this.getElement(startByte); 174 int a2 = this.getElement(startByte + 1); 175 return getTurnoutAddrFromData(a1, a2); 176 } else { 177 return -1; 178 } 179 } 180 181 /** 182 * Parse the feedback message for a turnout, and return the status for the 183 * even or odd half of the nibble (upper or lower part). 184 * 185 * @param turnout <ul> 186 * <li>0 for the even turnout associated with the pair. This is the upper 187 * half of the data nibble asociated with the pair </li> 188 * <li>1 for the odd turnout associated with the pair. This is the lower 189 * half of the data nibble asociated with the pair </li> 190 * </ul> 191 * @return THROWN/CLOSED as defined in {@link jmri.Turnout} 192 */ 193 public int getTurnoutStatus(int turnout) { 194 if (this.isFeedbackMessage() && (turnout == 0 || turnout == 1)) { 195 int a2 = this.getElement(2); 196 // fake turnout id, used just internally. Just odd/even matters. 197 return createFeedbackItem(turnout, a2).getTurnoutStatus(); 198 } 199 return (-1); 200 } 201 202 /** 203 * Parse the specified address byte/data byte pair in a feedback broadcast 204 * message and see if it is for a turnout. If it is, return the status for 205 * the even or odd half of the nibble (upper or lower part) 206 * 207 * @param startByte address byte of the address byte/data byte pair. 208 * @param turnout <ul> 209 * <li>0 for the even turnout associated with the pair. This is the upper 210 * half of the data nibble asociated with the pair </li> 211 * <li>1 for the odd turnout associated with the pair. This is the lower 212 * half of the data nibble asociated with the pair </li> 213 * </ul> 214 * @return THROWN/CLOSED as defined in {@link jmri.Turnout} 215 */ 216 public int getTurnoutStatus(int startByte, int turnout) { 217 if (this.isFeedbackBroadcastMessage() && (turnout == 0 || turnout == 1)) { 218 int a2 = this.getElement(startByte + 1); 219 // fake turnout id, used just internally. Just odd/even matters. 220 return createFeedbackItem(turnout, a2).getTurnoutStatus(); 221 } 222 return (-1); 223 } 224 225 /** 226 * If this is a feedback response message for a feedback encoder, return the 227 * address. Otherwise return -1. 228 * 229 * @return the integer address or -1 if not a feedback message 230 */ 231 public int getFeedbackEncoderMsgAddr() { 232 if (this.isFeedbackMessage()) { 233 int a1 = this.getElement(1); 234 int messagetype = this.getFeedbackMessageType(); 235 if (messagetype == 2) { 236 // This is a feedback encoder message 237 return (a1 & 0xff); 238 } else { 239 return -1; 240 } 241 } else { 242 return -1; 243 } 244 } 245 246 /** 247 * Returns the number of feedback items in the messages. 248 * For accessory info replies, always returns 1. For broadcast, it returns the 249 * number of feedback pairs. Returns 0 for non-feedback messages. 250 * 251 * @return number of feedback pair items. 252 */ 253 public final int getFeedbackMessageItems() { 254 if (isFeedbackMessage()) { 255 return 1; 256 } else if (isFeedbackBroadcastMessage()) { 257 return (this.getElement(0) & 0x0F) / 2; 258 } 259 return 0; 260 } 261 262 /** 263 * If this is a feedback broadcast message and the specified startByte is 264 * the address byte of an address byte/data byte pair for a feedback 265 * encoder, return the address. Otherwise return -1. 266 * 267 * @param startByte address byte of the address byte data byte pair. 268 * @return the integer address or -1 if not a feedback message 269 */ 270 public int getFeedbackEncoderMsgAddr(int startByte) { 271 if (this.isFeedbackBroadcastMessage()) { 272 int a1 = this.getElement(startByte); 273 int messagetype = this.getFeedbackMessageType(startByte); 274 if (messagetype == 2) { 275 // This is a feedback encoder message 276 return (a1 & 0xff); 277 } else { 278 return -1; 279 } 280 } else { 281 return -1; 282 } 283 } 284 285 /** 286 * Is this a feedback response message? 287 * @return true if a feedback response, else false. 288 */ 289 public boolean isFeedbackMessage() { 290 return (this.getElement(0) == XNetConstants.ACC_INFO_RESPONSE); 291 } 292 293 /** 294 * Is this a feedback broadcast message? 295 * @return true if a feedback broadcast message, else false. 296 */ 297 public boolean isFeedbackBroadcastMessage() { 298 return ((this.getElement(0) & 0xF0) == XNetConstants.BC_FEEDBACK); 299 } 300 301 /** 302 * Extract the feedback message type from a feedback message this is the 303 * middle two bits of the upper byte of the second data byte. 304 * 305 * @return message type, values are: 306 * <ul> 307 * <li>0 for a turnout with no feedback</li> 308 * <li>1 for a turnout with feedback</li> 309 * <li>2 for a feedback encoder</li> 310 * <li>3 is reserved by Lenz for future use.</li> 311 * </ul> 312 */ 313 public int getFeedbackMessageType() { 314 if (this.isFeedbackMessage()) { 315 int a2 = this.getElement(2); 316 return ((a2 & 0x60) / 32); 317 } else { 318 return -1; 319 } 320 } 321 322 /** 323 * Extract the feedback message type from the data byte of associated with 324 * the specified address byte specified by startByte. 325 * <p> 326 * The return value is the middle two bits of the upper byte of the data 327 * byte of an address byte/data byte pair. 328 * 329 * @param startByte The address byte for this addres byte data byte pair. 330 * @return message type, values are: 331 * <ul> 332 * <li>0 for a turnout with no feedback</li> 333 * <li>1 for a turnout with feedback</li> 334 * <li>2 for a feedback encoder</li> 335 * <li>3 is reserved by Lenz for future use.</li> 336 * </ul> 337 */ 338 public int getFeedbackMessageType(int startByte) { 339 if (this.isFeedbackBroadcastMessage()) { 340 int a2 = this.getElement(startByte + 1); 341 return ((a2 & 0x60) / 32); 342 } else { 343 return -1; 344 } 345 } 346 347 public boolean isFeedbackMotionComplete(int startByte) { 348 int messageType = getFeedbackMessageType(startByte); 349 if (messageType == 1) { 350 int a2 = getElement(startByte + 1); 351 return ((a2 & 0x80) != 0x80); 352 } 353 return false; 354 } 355 356 /* 357 * Next we have a few throttle related messages 358 */ 359 360 /** 361 * If this is a throttle-type message, return address. 362 * Otherwise return -1. 363 * <p> 364 * Note we only identify the command now; 365 * the response to a request for status is not yet seen here. 366 * @return address if throttle-type message, else -1. 367 */ 368 public int getThrottleMsgAddr() { 369 if (this.isThrottleMessage()) { 370 int a1 = this.getElement(2); 371 int a2 = this.getElement(3); 372 if (a1 == 0) { 373 return (a2); 374 } else { 375 return (((a1 * 256) & 0xFF00) + (a2 & 0xFF) - 0xC000); 376 } 377 } else { 378 return -1; 379 } 380 } 381 382 /** 383 * Is this a throttle message? 384 * @return true if throttle message. else false. 385 */ 386 public boolean isThrottleMessage() { 387 int message = this.getElement(0); 388 return (message == XNetConstants.LOCO_INFO_NORMAL_UNIT 389 || message == XNetConstants.LOCO_INFO_RESPONSE 390 || message == XNetConstants.LOCO_INFO_MUED_UNIT 391 || message == XNetConstants.LOCO_INFO_MU_ADDRESS 392 || message == XNetConstants.LOCO_INFO_DH_UNIT 393 || message == XNetConstants.LOCO_AVAILABLE_V1 394 || message == XNetConstants.LOCO_AVAILABLE_V2 395 || message == XNetConstants.LOCO_NOT_AVAILABLE_V1 396 || message == XNetConstants.LOCO_NOT_AVAILABLE_V2); 397 } 398 399 /** 400 * Does this message indicate the locomotive has been taken over by another 401 * device? 402 * @return true if take over message, else false. 403 */ 404 public boolean isThrottleTakenOverMessage() { 405 return (this.getElement(0) == XNetConstants.LOCO_INFO_RESPONSE 406 && this.getElement(1) == XNetConstants.LOCO_NOT_AVAILABLE); 407 } 408 409 /** 410 * Is this a consist message? 411 * @return true if consist message, else false. 412 */ 413 public boolean isConsistMessage() { 414 int message = this.getElement(0); 415 return (message == XNetConstants.LOCO_MU_DH_ERROR 416 || message == XNetConstants.LOCO_DH_INFO_V1 417 || message == XNetConstants.LOCO_DH_INFO_V2); 418 } 419 420 /* 421 * Finally, we have some commonly used routines that are used for 422 * checking specific, generic, response messages. 423 */ 424 425 /** 426 * In the interest of code reuse, the following function checks to see 427 * if an XpressNet Message is the OK message (01 04 05). 428 * @return true if an OK message, else false. 429 */ 430 public boolean isOkMessage() { 431 return (this.getElement(0) == XNetConstants.LI_MESSAGE_RESPONSE_HEADER 432 && this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_SEND_SUCCESS); 433 } 434 435 /** 436 * In the interest of code reuse, the following function checks to see 437 * if an XpressNet Message is the timeslot restored message (01 07 06). 438 * @return true if a time-slot restored message. 439 */ 440 public boolean isTimeSlotRestored() { 441 return (this.getElement(0) == XNetConstants.LI_MESSAGE_RESPONSE_HEADER 442 && this.getElement(1) == XNetConstants.LIUSB_TIMESLOT_RESTORED); 443 } 444 445 /** 446 * In the interest of code reuse, the following function checks to see 447 * if an XpressNet Message is the Command Station no longer providing a 448 * timeslot message (01 05 04). 449 * @return true if a time-slot revoked message, else false. 450 */ 451 public boolean isTimeSlotRevoked() { 452 return (this.getElement(0) == XNetConstants.LI_MESSAGE_RESPONSE_HEADER 453 && this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR); 454 } 455 456 /** 457 * In the interest of code reuse, the following function checks to see 458 * if an XpressNet Message is the Command Station Busy message (61 81 e3). 459 * @return true if is a CS Busy message, else false. 460 */ 461 public boolean isCSBusyMessage() { 462 return (this.getElement(0) == XNetConstants.CS_INFO 463 && this.getElement(1) == XNetConstants.CS_BUSY); 464 } 465 466 467 /** 468 * In the interest of code reuse, the following function checks to see 469 * if an XpressNet Message is the Command Station Transfer Error 470 * message (61 80 e1). 471 * @return if CS Transfer error, else false. 472 */ 473 public boolean isCSTransferError() { 474 return (this.getElement(0) == XNetConstants.CS_INFO 475 && this.getElement(1) == XNetConstants.CS_TRANSFER_ERROR); 476 } 477 478 /** 479 * In the interest of code reuse, the following function checks to see 480 * if an XpressNet Message is the not supported Error 481 * message (61 82 e3). 482 * @return true if unsupported error, else false. 483 */ 484 public boolean isUnsupportedError() { 485 return (this.getElement(0) == XNetConstants.CS_INFO 486 && this.getElement(1) == XNetConstants.CS_NOT_SUPPORTED); 487 } 488 489 /** 490 * In the interest of code reuse, the following function checks to see 491 * if an XpressNet Message is a communications error message. 492 * <p> 493 * The errors handled are: 494 * 01 01 00 -- Error between interface and the PC 495 * 01 02 03 -- Error between interface and the Command Station 496 * 01 03 02 -- Unknown Communications Error 497 * 01 06 07 -- LI10x Buffer Overflow 498 * 01 0A 0B -- LIUSB only. Request resend of data. 499 * @return true if comm error message, else false. 500 */ 501 public boolean isCommErrorMessage() { 502 return (this.getElement(0) == XNetConstants.LI_MESSAGE_RESPONSE_HEADER 503 && (this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_UNKNOWN_DATA_ERROR 504 || this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_CS_DATA_ERROR 505 || this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_PC_DATA_ERROR 506 || this.getElement(1) == XNetConstants.LIUSB_RETRANSMIT_REQUEST 507 || this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_BUFFER_OVERFLOW) 508 || this.isTimeSlotErrorMessage()); 509 } 510 511 /** 512 * In the interest of code reuse, the following function checks to see 513 * if an XpressNet Message is a communications error message. 514 * <p> 515 * The errors handled are: 516 * 01 05 04 -- Timeslot Error 517 * 01 07 06 -- Timeslot Restored 518 * 01 08 09 -- Data sent while there is no Timeslot 519 * @return true if time slot error, else false. 520 */ 521 public boolean isTimeSlotErrorMessage() { 522 return (this.getElement(0) == XNetConstants.LI_MESSAGE_RESPONSE_HEADER 523 && (this.getElement(1) == XNetConstants.LIUSB_REQUEST_SENT_WHILE_NO_TIMESLOT 524 || this.getElement(1) == XNetConstants.LIUSB_TIMESLOT_RESTORED 525 || this.getElement(1) == XNetConstants.LI_MESSAGE_RESPONSE_TIMESLOT_ERROR)); 526 } 527 528 529 /** 530 * Is this message a service mode response? 531 * @return true if a service mode response, else false. 532 */ 533 public boolean isServiceModeResponse() { 534 return (getElement(0) == XNetConstants.CS_SERVICE_MODE_RESPONSE 535 && (getElement(1) == XNetConstants.CS_SERVICE_DIRECT_RESPONSE 536 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 1) 537 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 2) 538 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 3) 539 || getElement(1) == XNetConstants.CS_SERVICE_REG_PAGE_RESPONSE)); 540 } 541 542 /** 543 * Is this message a register or paged mode programming response? 544 * @return true if register or paged mode programming response, else false. 545 */ 546 public boolean isPagedModeResponse() { 547 return (getElement(0) == XNetConstants.CS_SERVICE_MODE_RESPONSE 548 && getElement(1) == XNetConstants.CS_SERVICE_REG_PAGE_RESPONSE); 549 } 550 551 /** 552 * Is this message a direct CV mode programming response? 553 * @return true if direct CV mode programming response, else false. 554 */ 555 public boolean isDirectModeResponse() { 556 return (getElement(0) == XNetConstants.CS_SERVICE_MODE_RESPONSE 557 && (getElement(1) == XNetConstants.CS_SERVICE_DIRECT_RESPONSE 558 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 1) 559 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 2) 560 || getElement(1) == (XNetConstants.CS_SERVICE_DIRECT_RESPONSE + 3))); 561 } 562 563 /** 564 * @return the CV value associated with a service mode reply 565 * return -1 if not a service mode message. 566 */ 567 public int getServiceModeCVNumber() { 568 int cv = -1; 569 if (isServiceModeResponse()) { 570 if ((getElement(1) & XNetConstants.CS_SERVICE_DIRECT_RESPONSE) == XNetConstants.CS_SERVICE_DIRECT_RESPONSE) { 571 cv = (getElement(1) - XNetConstants.CS_SERVICE_DIRECT_RESPONSE) * 256 + getElement(2); 572 } else { 573 cv = getElement(2); 574 } 575 } 576 return (cv); 577 } 578 579 /** 580 * @return the value returned by the DCC system associated with a 581 * service mode reply. 582 * return -1 if not a service mode message. 583 */ 584 public int getServiceModeCVValue() { 585 int value = -1; 586 if (isServiceModeResponse()) { 587 value = getElement(3); 588 } 589 return (value); 590 } 591 592 /** 593 * @return true if the message is an error message indicating 594 * we should retransmit. 595 */ 596 @Override 597 public boolean isRetransmittableErrorMsg() { 598 return (this.isCSBusyMessage() 599 || this.isCommErrorMessage() 600 || this.isCSTransferError()); 601 } 602 603 /** 604 * @return true if the message is an unsolicited message 605 */ 606 @Override 607 public boolean isUnsolicited() { 608 // The message may be set as an unsolicited message else where 609 // or it may be classified as unsolicited based on the type of 610 // message received. 611 // NOTE: The feedback messages may be received in either solicited 612 // or unsolicited form. requesting code can mark the reply as solicited 613 // by calling the resetUnsolicited function. 614 return (super.isUnsolicited() 615 || this.isThrottleTakenOverMessage()); 616 } 617 618 /** 619 * Mask to identify a turnout feedback + correct nibble. Turnout types differ in 620 * 6th bit, so it's left out (is variable). 621 */ 622 private static final int FEEDBACK_TURNOUT_MASK = 0b0101_0000; 623 624 /** 625 * Mask to identify a feedback module + correct nibble. Turnout modules have 626 * type exactly 2. 627 */ 628 private static final int FEEDBACK_MODULE_MASK = 0b0111_0000; 629 630 /** 631 * The value of "feedback module" type. 632 */ 633 private static final int FEEDBACK_TYPE_FBMODULE = 0b0100_0000; 634 635 /** 636 * Bit that indicates the higher nibble in module or turnout feedback 637 */ 638 private static final int FEEDBACK_HIGH_NIBBLE = 0b0001_0000; 639 640 private int findFeedbackData(int baseAddress, int selector, int mask) { 641 if (isFeedbackMessage()) { 642 // shorctcut for single-item msg 643 int data = getElement(2); 644 if (getElement(1) == baseAddress && 645 (data & mask) == selector) { 646 return data; 647 } 648 } else { 649 int start = 1; 650 for (int cnt = getFeedbackMessageItems(); cnt > 0; cnt--, start += 2) { 651 int data = getElement(start + 1); 652 if (getElement(start) == baseAddress && 653 (data & mask) == selector) { 654 return data; 655 } 656 } 657 } 658 return -1; 659 } 660 661 /** 662 * Returns value of the given feedback module bit. Returns {@link Optional} 663 * that is non-empty, if the feedback was present. The Optional's value indicates the 664 * feedback state. 665 * 666 * @param sensorNumber the sensor bit ID 667 * @return optional sensor state. 668 */ 669 @CheckForNull 670 public Boolean selectModuleFeedback(int sensorNumber) { 671 if (!isFeedbackBroadcastMessage() || sensorNumber == 0 || sensorNumber >= 1024) { 672 return null; 673 } 674 // feedback address directly addresses 8-bit module, XpressNet spec 3.0:2.1.11. 675 int s = sensorNumber - 1; 676 int baseAddress = (s / 8); 677 int selector2 = (s & 0x04) != 0 ? 678 FEEDBACK_TYPE_FBMODULE | FEEDBACK_HIGH_NIBBLE : 679 FEEDBACK_TYPE_FBMODULE; 680 int res = findFeedbackData(baseAddress, selector2, FEEDBACK_MODULE_MASK); 681 return res == -1 ? null : 682 (res & (1 << (s % 4))) > 0; 683 } 684 685 /** 686 * Calls processor for turnout's feedback, returns the processor's outcome. 687 * Searches for the turnout feedback for the given accessory. If found, 688 * runs a processor on the feedback item, and returns its Boolean result. 689 * <p> 690 * Returns {@code false}, if matching feedback is not found. 691 * @param accessoryNumber the turnout number 692 * @param proc the processor 693 * @return {@code false} if feedback was not found, or a result of {@code proc()}. 694 */ 695 public boolean onTurnoutFeedback(int accessoryNumber, Function<FeedbackItem, Boolean> proc) { 696 return selectTurnoutFeedback(accessoryNumber).map(proc).orElse(false); 697 } 698 699 /** 700 * Selects a matching turnout feedback. Finds turnout feedback for the given {@code accessoryNumber}. 701 * Returns an encapsulated feedback, that can be inspected. If no matching feedback is 702 * present, returns empty {@link Optional}. 703 * @param accessoryNumber the turnout number 704 * @return optional feedback item. 705 */ 706 @Nonnull 707 public Optional<FeedbackItem> selectTurnoutFeedback(int accessoryNumber) { 708 // shortcut for single-item messages. 709 if (!isFeedbackBroadcastMessage() || accessoryNumber <= 0 || accessoryNumber >= 1024) { 710 return Optional.empty(); 711 } 712 int a = accessoryNumber - 1; 713 int base = (a / 4); 714 // the mask makes the turnout feedback type bit irrelevant 715 int selector2 = (a & 0x02) != 0 ? FEEDBACK_HIGH_NIBBLE : 0; 716 int r = findFeedbackData(base, selector2, FEEDBACK_TURNOUT_MASK); 717 if (r == -1) { 718 return Optional.empty(); 719 } 720 FeedbackItem item = new FeedbackItem(this, accessoryNumber, r); 721 return Optional.of(item); 722 } 723 724 protected final FeedbackItem createFeedbackItem(int n, int d) { 725 return new FeedbackItem(this, n, d); 726 } 727 728 private static final List<XPressNetMessageFormatter> formatterList = new ArrayList<>(); 729 /** 730 * @return a string representation of the reply suitable for display in the 731 * XpressNet monitor. 732 */ 733 @Override 734 public String toMonitorString() { 735 736 if (formatterList.isEmpty()) { 737 try { 738 Reflections reflections = new Reflections("jmri.jmrix"); 739 Set<Class<? extends XPressNetMessageFormatter>> f = reflections.getSubTypesOf(XPressNetMessageFormatter.class); 740 for (Class<?> c : f) { 741 log.debug("Found formatter: {}", f.getClass().getName()); 742 Constructor<?> ctor = c.getConstructor(); 743 formatterList.add((XPressNetMessageFormatter) ctor.newInstance()); 744 } 745 } catch (NoSuchMethodException | SecurityException | InstantiationException | 746 IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 747 log.error("Error instantiating formatter", e); 748 } 749 } 750 751 return formatterList.stream().filter(f -> f.handlesMessage(this)).findFirst().map(f -> f.formatMessage(this)).orElse(this.toString()); 752 } 753 754 // initialize logging 755 private static final Logger log = LoggerFactory.getLogger(XNetReply.class); 756 757}